huge blog refactor
tidying up data structures; improvements to blog admin UI/UX, etc.
This commit is contained in:
parent
eaa2f6587d
commit
0c2aaa0b38
18 changed files with 432 additions and 239 deletions
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
.blog-collection {
|
||||
margin-bottom: 1em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .5em;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<div class="blogpost">
|
||||
<h3 class="title"><a href="/admin/blogs/{{.ID}}">{{.Title}}</a>{{if not .Visible}} <small>(Not published)</small>{{end}}</h3>
|
||||
<p class="meta">
|
||||
<span class="author"><img src="/img/favicon.png" alt="{{.Author.Username}}'s avatar" width="32" height="32"/> {{.Author.Username}}</span>
|
||||
<span class="author"><img src="/img/favicon.png" alt="" width="32" height="32"/> {{.Author.DisplayName}}</span>
|
||||
<span class="date">• {{.PrintDate}}</span>
|
||||
</p>
|
||||
<p class="description">{{.Description}}</p>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@
|
|||
>{{.Post.Title}}</div>
|
||||
</h2>
|
||||
|
||||
<label for="publish-date">Publish Date</label>
|
||||
<input type="datetime-local" name="publish-date" id="publish-date" value="{{.Post.TextPublishDate}}">
|
||||
|
||||
<label for="description">Description</label>
|
||||
<textarea
|
||||
id="description"
|
||||
|
|
@ -43,20 +46,47 @@
|
|||
rows="30"
|
||||
>{{.Post.Markdown}}</textarea>
|
||||
|
||||
<label for="bluesky-actor">Bluesky Author DID</label>
|
||||
<input
|
||||
type="text"
|
||||
name="bluesky-actor"
|
||||
id="bluesky-actor"
|
||||
placeholder="did:plc:1234abcd..."
|
||||
value="{{if .Post.BlueskyActorID}}{{.Post.BlueskyActorID}}{{end}}">
|
||||
<label for="bluesky-post">Bluesky Post ID</label>
|
||||
<input
|
||||
type="text"
|
||||
name="bluesky-post"
|
||||
id="bluesky-post"
|
||||
placeholder="3m109a03..."
|
||||
value="{{if .Post.BlueskyPostID}}{{.Post.BlueskyPostID}}{{end}}">
|
||||
<div class="social-post-details">
|
||||
<div class="social-post-item">
|
||||
<label for="bluesky-actor">Bluesky Author DID</label>
|
||||
<input
|
||||
type="text"
|
||||
name="bluesky-actor"
|
||||
id="bluesky-actor"
|
||||
placeholder="did:plc:1234abcd..."
|
||||
value="{{if .Post.Bluesky}}{{.Post.Bluesky.ActorDID}}{{end}}">
|
||||
</div>
|
||||
<div class="social-post-item">
|
||||
<label for="bluesky-record">Bluesky Post ID</label>
|
||||
<input
|
||||
type="text"
|
||||
name="bluesky-record"
|
||||
id="bluesky-record"
|
||||
placeholder="3m109a03..."
|
||||
value="{{if .Post.Bluesky}}{{.Post.Bluesky.RecordID}}{{end}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="social-post-details">
|
||||
<div class="social-post-item">
|
||||
<label for="fediverse-account">Fediverse Account</label>
|
||||
<input
|
||||
type="text"
|
||||
name="fediverse-account"
|
||||
id="fediverse-account"
|
||||
placeholder="@me@my.fediverse.place"
|
||||
value="{{if .Post.Fediverse}}{{.Post.Fediverse.AccountID}}{{end}}">
|
||||
</div>
|
||||
<div class="social-post-item">
|
||||
<label for="fediverse-status">Fediverse Status ID</label>
|
||||
<input
|
||||
type="text"
|
||||
name="fediverse-status"
|
||||
id="fediverse-status"
|
||||
placeholder="never consistent ¯\_(ツ)_/¯"
|
||||
value="{{if .Post.Fediverse}}{{.Post.Fediverse.StatusID}}{{end}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label for="visibility">Visibility</label>
|
||||
<select name="visibility" id="visibility">
|
||||
|
|
|
|||
|
|
@ -59,8 +59,8 @@
|
|||
<h2><a href="/admin/blogs/">Latest Blog Post</a> <small>({{.BlogCount}} total)</small></h2>
|
||||
<a class="button new" id="create-post">Create New</a>
|
||||
</div>
|
||||
{{if .BlogPost}}
|
||||
{{block "blogpost" .BlogPost}}{{end}}
|
||||
{{if .LatestBlogPost}}
|
||||
{{block "blogpost" .LatestBlogPost}}{{end}}
|
||||
{{else}}
|
||||
<p>There are no blog posts.</p>
|
||||
{{end}}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue