From 0c2aaa0b388cf3c51da17456a2f8a1befff30526 Mon Sep 17 00:00:00 2001 From: ari melody Date: Sat, 8 Nov 2025 12:54:31 +0000 Subject: [PATCH] huge blog refactor tidying up data structures; improvements to blog admin UI/UX, etc. --- admin/blog/blog.go | 68 ++++---- admin/http.go | 19 +-- admin/static/blog.css | 1 + admin/static/edit-blog.css | 19 ++- admin/static/edit-blog.js | 34 ++-- .../html/components/blog/blogpost.html | 2 +- admin/templates/html/edit-blog.html | 58 +++++-- admin/templates/html/index.html | 4 +- api/blog.go | 127 +++++++++----- controller/blog.go | 161 +++++++++++++----- controller/bluesky.go | 4 +- controller/schema-migration/000-init.sql | 6 +- controller/schema-migration/004-blog.sql | 6 +- model/blog.go | 48 +++--- public/style/blogpost.css | 5 + templates/html/blog.html | 4 +- templates/html/blogpost.html | 10 +- view/blog.go | 95 +++++------ 18 files changed, 432 insertions(+), 239 deletions(-) diff --git a/admin/blog/blog.go b/admin/blog/blog.go index 294ac66..d03907a 100644 --- a/admin/blog/blog.go +++ b/admin/blog/blog.go @@ -23,17 +23,19 @@ func Handler(app *model.AppState) http.Handler { } type ( - blogPost struct { - *model.BlogPost - Author *model.Account - } - - blogPostGroup struct { + blogPostCollection struct { Year int - Posts []*blogPost + Posts []*model.BlogPost } ) +func (c *blogPostCollection) Clone() blogPostCollection { + return blogPostCollection{ + Year: c.Year, + Posts: slices.Clone(c.Posts), + } +} + func serveBlogIndex(app *model.AppState) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { session := r.Context().Value("session").(*model.Session) @@ -45,44 +47,36 @@ func serveBlogIndex(app *model.AppState) http.Handler { return } - collections := []*blogPostGroup{} - collectionPosts := []*blogPost{} - collectionYear := -1 + collections := []*blogPostCollection{} + collection := blogPostCollection{ + Posts: []*model.BlogPost{}, + Year: -1, + } for i, post := range posts { - author, err := controller.GetAccountByID(app.DB, post.AuthorID) - if err != nil { - fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve author of blog %s: %v\n", post.ID, err) - continue + if i == 0 { + collection.Year = post.PublishDate.Year() } - if collectionYear == -1 { - collectionYear = post.CreatedAt.Year() - } - - authoredPost := blogPost{ - BlogPost: post, - Author: author, - } - - if post.CreatedAt.Year() != collectionYear || i == len(posts) - 1 { - if i == len(posts) - 1 { - collectionPosts = append([]*blogPost{&authoredPost}, collectionPosts...) + if post.PublishDate.Year() != collection.Year { + clone := collection.Clone() + collections = append(collections, &clone) + collection = blogPostCollection{ + Year: post.PublishDate.Year(), + Posts: []*model.BlogPost{}, } - collections = append(collections, &blogPostGroup{ - Year: collectionYear, - Posts: slices.Clone(collectionPosts), - }) - collectionPosts = []*blogPost{} - collectionYear = post.CreatedAt.Year() } - collectionPosts = append(collectionPosts, &authoredPost) + collection.Posts = append(collection.Posts, post) + + if i == len(posts) - 1 { + collections = append(collections, &collection) + } } type blogsData struct { core.AdminPageData TotalPosts int - Collections []*blogPostGroup + Collections []*blogPostCollection } err = templates.BlogsTemplate.Execute(w, blogsData{ @@ -108,7 +102,11 @@ func serveBlogPost(app *model.AppState) http.Handler { post, err := controller.GetBlogPost(app.DB, blogID) if err != nil { - fmt.Printf("can't find blog with ID %s\n", blogID) + fmt.Fprintf(os.Stderr, "WARN: Failed to fetch blog %s: %v\n", blogID, err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + if post == nil { http.NotFound(w, r) return } diff --git a/admin/http.go b/admin/http.go index 55265cb..33f017b 100644 --- a/admin/http.go +++ b/admin/http.go @@ -109,19 +109,8 @@ func adminIndexHandler(app *model.AppState) http.Handler { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } - var latestBlogPost *BlogPost = nil - if len(blogPosts) > 0 { - author, err := controller.GetAccountByID(app.DB, blogPosts[0].AuthorID) - if err != nil { - fmt.Fprintf(os.Stderr, "WARN: Failed to pull latest blog post author %s: %v\n", blogPosts[0].AuthorID, err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return - } - latestBlogPost = &BlogPost{ - BlogPost: blogPosts[0], - Author: author, - } - } + var latestBlogPost *model.BlogPost = nil + if len(blogPosts) > 0 { latestBlogPost = blogPosts[0] } blogCount, err := controller.GetBlogPostCount(app.DB, false) if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to pull blog post count: %v\n", err) @@ -137,7 +126,7 @@ func adminIndexHandler(app *model.AppState) http.Handler { ArtistCount int Tracks []*model.Track TrackCount int - BlogPost *BlogPost + LatestBlogPost *model.BlogPost BlogCount int } @@ -149,7 +138,7 @@ func adminIndexHandler(app *model.AppState) http.Handler { ArtistCount: artistCount, Tracks: tracks, TrackCount: trackCount, - BlogPost: latestBlogPost, + LatestBlogPost: latestBlogPost, BlogCount: blogCount, }) if err != nil { diff --git a/admin/static/blog.css b/admin/static/blog.css index 72d3cef..c2845b4 100644 --- a/admin/static/blog.css +++ b/admin/static/blog.css @@ -1,4 +1,5 @@ .blog-collection { + margin-bottom: 1em; display: flex; flex-direction: column; gap: .5em; diff --git a/admin/static/edit-blog.css b/admin/static/edit-blog.css index 86626d6..5e86b4b 100644 --- a/admin/static/edit-blog.css +++ b/admin/static/edit-blog.css @@ -5,7 +5,7 @@ input[type="text"] { border: none; border-radius: 4px; outline: none; - color: inherit; + color: var(--fg-3); background-color: var(--bg-1); box-shadow: var(--shadow-sm); } @@ -67,6 +67,17 @@ input[type="text"] { background-color: var(--bg-3); } +#blogpost #publish-date { + padding: .4em .5em; + font-family: inherit; + font-size: inherit; + border-radius: 4px; + border: none; + background-color: var(--bg-1); + color: var(--fg-3); + box-shadow: var(--shadow-sm); +} + #blogpost textarea { width: calc(100% - 2em); margin: 0; @@ -95,6 +106,12 @@ input[type="text"] { box-shadow: var(--shadow-sm); } +#blogpost .social-post-details { + margin: 1em 0 1em 0; + display: flex; + gap: 1em; +} + #blogpost .blog-actions { margin-top: 1em; } diff --git a/admin/static/edit-blog.js b/admin/static/edit-blog.js index 642c69c..9047ee9 100644 --- a/admin/static/edit-blog.js +++ b/admin/static/edit-blog.js @@ -1,9 +1,12 @@ const blogID = document.getElementById("blogpost").dataset.id; const titleInput = document.getElementById("title"); +const publishDateInput = document.getElementById("publish-date"); const descInput = document.getElementById("description"); const mdInput = document.getElementById("markdown"); const blueskyActorInput = document.getElementById("bluesky-actor"); -const blueskyPostInput = document.getElementById("bluesky-post"); +const blueskyRecordInput = document.getElementById("bluesky-record"); +const fediverseAccountInput = document.getElementById("fediverse-account"); +const fediverseStatusInput = document.getElementById("fediverse-status"); const visInput = document.getElementById("visibility"); const saveBtn = document.getElementById("save"); const deleteBtn = document.getElementById("delete"); @@ -12,11 +15,18 @@ saveBtn.addEventListener("click", () => { fetch("/api/v1/blog/" + blogID, { method: "PUT", body: JSON.stringify({ - title: titleInput.value, + title: titleInput.innerText, + publish_date: publishDateInput.value + ":00Z", description: descInput.value, markdown: mdInput.value, - bluesky_actor: blueskyActorInput.value, - bluesky_post: blueskyPostInput.value, + bluesky: { + actor: blueskyActorInput.value, + record: blueskyRecordInput.value, + }, + fediverse: { + account: fediverseAccountInput.value, + status: fediverseStatusInput.value, + }, visible: visInput.value === "true", }), headers: { "Content-Type": "application/json" } @@ -53,11 +63,13 @@ deleteBtn.addEventListener("click", () => { }); }); -[titleInput, descInput, mdInput, blueskyActorInput, blueskyPostInput, visInput].forEach(input => { - input.addEventListener("change", () => { - saveBtn.disabled = false; +[titleInput, publishDateInput, descInput, mdInput, visInput, + blueskyActorInput, blueskyRecordInput, + fediverseAccountInput, fediverseStatusInput].forEach(input => { + input.addEventListener("change", () => { + saveBtn.disabled = false; + }); + input.addEventListener("keypress", () => { + saveBtn.disabled = false; + }); }); - input.addEventListener("keypress", () => { - saveBtn.disabled = false; - }); -}); diff --git a/admin/templates/html/components/blog/blogpost.html b/admin/templates/html/components/blog/blogpost.html index 984bdca..11771d0 100644 --- a/admin/templates/html/components/blog/blogpost.html +++ b/admin/templates/html/components/blog/blogpost.html @@ -2,7 +2,7 @@

{{.Title}}{{if not .Visible}} (Not published){{end}}

- {{.Author.Username}}'s avatar {{.Author.Username}} + {{.Author.DisplayName}} • {{.PrintDate}}

{{.Description}}

diff --git a/admin/templates/html/edit-blog.html b/admin/templates/html/edit-blog.html index 71570ee..eba577d 100644 --- a/admin/templates/html/edit-blog.html +++ b/admin/templates/html/edit-blog.html @@ -26,6 +26,9 @@ >{{.Post.Title}}
+ + + - - - - +
+ + +
+ +
+ + +