From 82fd17c836b92c29b0cef18f53192b1f313075a2 Mon Sep 17 00:00:00 2001 From: ari melody Date: Fri, 7 Nov 2025 02:35:51 +0000 Subject: [PATCH] early admin edit blog page --- admin/blog/blog.go | 49 ++++++++---- admin/static/edit-blog.css | 100 +++++++++++++++++++++++++ admin/static/edit-blog.js | 63 ++++++++++++++++ admin/templates/html/edit-blog.html | 87 +++++++++++++++++++++ admin/templates/html/edit-release.html | 2 +- admin/templates/templates.go | 9 +++ model/blog.go | 2 +- 7 files changed, 297 insertions(+), 15 deletions(-) create mode 100644 admin/static/edit-blog.css create mode 100644 admin/static/edit-blog.js create mode 100644 admin/templates/html/edit-blog.html diff --git a/admin/blog/blog.go b/admin/blog/blog.go index 322b7db..806f34c 100644 --- a/admin/blog/blog.go +++ b/admin/blog/blog.go @@ -15,12 +15,8 @@ func Handler(app *model.AppState) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mux := http.NewServeMux() - mux.Handle("/{id}", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - blogID := r.PathValue("id") - w.Write([]byte(blogID)) - })) - - mux.Handle("/", handleBlogIndex(app)) + mux.Handle("/{id}", serveBlogPost(app)) + mux.Handle("/", serveBlogIndex(app)) mux.ServeHTTP(w, r) }) @@ -38,14 +34,9 @@ type ( } ) -func handleBlogIndex(app *model.AppState) http.Handler { +func serveBlogIndex(app *model.AppState) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - session, err := controller.GetSessionFromRequest(app, r) - if err != nil { - fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve session: %v\n", err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return - } + session := r.Context().Value("session").(*model.Session) posts, err := controller.GetBlogPosts(app.DB, false, -1, 0) if err != nil { @@ -108,3 +99,35 @@ func handleBlogIndex(app *model.AppState) http.Handler { } }) } + +func serveBlogPost(app *model.AppState) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + session := r.Context().Value("session").(*model.Session) + + blogID := r.PathValue("id") + + post, err := controller.GetBlogPost(app.DB, blogID) + if err != nil { + fmt.Printf("can't find blog with ID %s\n", blogID) + http.NotFound(w, r) + return + } + + type blogPostData struct { + core.AdminPageData + Post *model.BlogPost + } + + err = templates.EditBlogTemplate.Execute(w, blogPostData{ + AdminPageData: core.AdminPageData{ + Session: session, + }, + Post: post, + }) + if err != nil { + fmt.Fprintf(os.Stderr, "WARN: Error rendering admin edit page for blog %s: %v\n", blogID, err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + }) +} diff --git a/admin/static/edit-blog.css b/admin/static/edit-blog.css new file mode 100644 index 0000000..86626d6 --- /dev/null +++ b/admin/static/edit-blog.css @@ -0,0 +1,100 @@ +input[type="text"] { + padding: .3em .5em; + font-size: inherit; + font-family: inherit; + border: none; + border-radius: 4px; + outline: none; + color: inherit; + background-color: var(--bg-1); + box-shadow: var(--shadow-sm); +} + +#blogpost { + margin-bottom: 1em; + padding: 1.5em; + + border-radius: 8px; + background-color: var(--bg-2); + box-shadow: var(--shadow-lg); + + transition: background .1s ease-out, color .1s ease-out; +} + +#blogpost label { + margin: 1.2em 0 .2em .1em; + display: block; + font-size: .8em; + text-transform: uppercase; + font-weight: 600; +} +#blogpost label:first-of-type { + margin-top: 0; +} + +#blogpost h2 { + margin: 0; + font-size: 2em; +} + +#blogpost #title { + width: 100%; + margin: 0 -.2em; + padding: 0 .2em; + resize: none; + font-family: inherit; + font-size: inherit; + font-weight: bold; + border-radius: 4px; + border: 1px solid transparent; + background: transparent; + color: var(--fg-3); + outline: none; + cursor: pointer; + transition: background .1s ease-out, border-color .1s ease-out; + + /*position: relative; outline: none;*/ + white-space: pre-wrap; overflow-wrap: break-word; +} + +#blogpost #title:hover { + background-color: var(--bg-3); + border-color: var(--fg-0); +} + +#blogpost #title:active, +#blogpost #title:focus { + background-color: var(--bg-3); +} + +#blogpost textarea { + width: calc(100% - 2em); + margin: 0; + padding: 1em; + display: block; + border: none; + border-radius: 4px; + background-color: var(--bg-1); + color: var(--fg-3); + box-shadow: var(--shadow-md); + resize: vertical; + outline: none; +} + +#blogpost #description { + font-family: inherit; +} + +#blogpost select { + padding: .5em .8em; + font-size: inherit; + border: none; + border-radius: 10em; + color: var(--fg-3); + background-color: var(--bg-1); + box-shadow: var(--shadow-sm); +} + +#blogpost .blog-actions { + margin-top: 1em; +} diff --git a/admin/static/edit-blog.js b/admin/static/edit-blog.js new file mode 100644 index 0000000..642c69c --- /dev/null +++ b/admin/static/edit-blog.js @@ -0,0 +1,63 @@ +const blogID = document.getElementById("blogpost").dataset.id; +const titleInput = document.getElementById("title"); +const descInput = document.getElementById("description"); +const mdInput = document.getElementById("markdown"); +const blueskyActorInput = document.getElementById("bluesky-actor"); +const blueskyPostInput = document.getElementById("bluesky-post"); +const visInput = document.getElementById("visibility"); +const saveBtn = document.getElementById("save"); +const deleteBtn = document.getElementById("delete"); + +saveBtn.addEventListener("click", () => { + fetch("/api/v1/blog/" + blogID, { + method: "PUT", + body: JSON.stringify({ + title: titleInput.value, + description: descInput.value, + markdown: mdInput.value, + bluesky_actor: blueskyActorInput.value, + bluesky_post: blueskyPostInput.value, + visible: visInput.value === "true", + }), + headers: { "Content-Type": "application/json" } + }).then(res => { + if (!res.ok) { + res.text().then(error => { + console.error(error); + alert("Failed to update blog post: " + error); + }); + return; + } + + location = location; + }); +}); + +deleteBtn.addEventListener("click", () => { + if (blogID != prompt( + "You are about to permanently delete " + blogID + ". " + + "This action is irreversible. " + + "Please enter \"" + blogID + "\" to continue.")) return; + fetch("/api/v1/blog/" + blogID, { + method: "DELETE", + }).then(res => { + if (!res.ok) { + res.text().then(error => { + console.error(error); + alert("Failed to delete blog post: " + error); + }); + return; + } + + location = "/admin"; + }); +}); + +[titleInput, descInput, mdInput, blueskyActorInput, blueskyPostInput, visInput].forEach(input => { + input.addEventListener("change", () => { + saveBtn.disabled = false; + }); + input.addEventListener("keypress", () => { + saveBtn.disabled = false; + }); +}); diff --git a/admin/templates/html/edit-blog.html b/admin/templates/html/edit-blog.html new file mode 100644 index 0000000..71570ee --- /dev/null +++ b/admin/templates/html/edit-blog.html @@ -0,0 +1,87 @@ +{{define "head"}} +Editing {{.Post.Title}} - ari melody 💫 + + +{{end}} + +{{define "content"}} +
+

Editing Blog Post

+ +
+ +

+
{{.Post.Title}}
+

+ + + + + + + + + + + + + + + +
+ View + +
+
+ +
+
+

Danger Zone

+
+

+ Clicking the button below will delete this blog post. + This action is irreversible. + You will be prompted to confirm this decision. +

+ +
+
+ + +{{end}} diff --git a/admin/templates/html/edit-release.html b/admin/templates/html/edit-release.html index df3c48a..1ca31fe 100644 --- a/admin/templates/html/edit-release.html +++ b/admin/templates/html/edit-release.html @@ -8,7 +8,7 @@ {{define "content"}}
-

Editing {{.Release.Title}}

+

Editing Release

diff --git a/admin/templates/templates.go b/admin/templates/templates.go index 23632b7..6244540 100644 --- a/admin/templates/templates.go +++ b/admin/templates/templates.go @@ -200,3 +200,12 @@ var BlogsTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse( componentBlogPostHTML, }, "\n"), )) + +//go:embed "html/edit-blog.html" +var editBlogHTML string +var EditBlogTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse( + strings.Join([]string{ + editBlogHTML, + componentBlogPostHTML, + }, "\n"), +)) diff --git a/model/blog.go b/model/blog.go index d341154..4a2d03a 100644 --- a/model/blog.go +++ b/model/blog.go @@ -21,7 +21,7 @@ type ( Markdown string `db:"markdown"` HTML template.HTML `db:"html"` BlueskyActorID *string `db:"bluesky_actor"` - BlueskyPostID *string `db:"bluesky_post"` + BlueskyPostID *string `db:"bluesky_post"` } )