diff options
| author | grm <grm@eyesin.space> | 2026-09-15 23:12:51 +0300 |
|---|---|---|
| committer | grm <grm@eyesin.space> | 2026-09-15 23:12:51 +0300 |
| commit | 2c85ca1a46ddf4652f8fffc53d6bcfbe32b2b4ff (patch) | |
| tree | ecd5efd574b7d7463e4b2c4361c1994c4f28f561 /cmd | |
| parent | 55732bdfc25933bab26220ccd82fe5e172d9e673 (diff) | |
| download | blogspace-2c85ca1a46ddf4652f8fffc53d6bcfbe32b2b4ff.tar.gz blogspace-2c85ca1a46ddf4652f8fffc53d6bcfbe32b2b4ff.tar.bz2 blogspace-2c85ca1a46ddf4652f8fffc53d6bcfbe32b2b4ff.zip | |
Add post tags, with a tag page and two side-column modules
Posts can now carry tags, set on the post form as a checklist of the
blog's existing tags plus a comma-separated box for new ones (no JS).
A tag is a name and a unique slug, so "Go" and "go" are one tag and
Greek tags get readable URLs; tags no post uses any more are deleted.
On the blog, tags appear under the post date and link to /tag/<slug>,
which lists the published posts from every page, paginated like a page.
Two new layout modules show them: a Tags list (with counts, by use) and
a Tag cloud (alphabetical, sized by use). Both are only fetched when a
visible module needs them.
The article loop and pager move to a shared postlist partial; while
there, the pager stops adding a trailing slash — /news/?p=2 was a 404
because a {page} wildcard never matches one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
Diffstat (limited to 'cmd')
| -rw-r--r-- | cmd/blogspace/seed.go | 40 |
1 files changed, 33 insertions, 7 deletions
diff --git a/cmd/blogspace/seed.go b/cmd/blogspace/seed.go index 05c54f8..14c378f 100644 --- a/cmd/blogspace/seed.go +++ b/cmd/blogspace/seed.go @@ -97,6 +97,8 @@ func seed(ctx context.Context, cfg *config.Config, st *store.Store) error { {Area: "footer", Kind: "text", Body: "© Alice — made with Blogspace"}, {Area: "right", Kind: "recent", Title: "Recent posts", Count: 5}, {Area: "right", Kind: "archive", Title: "Archive"}, + {Area: "right", Kind: "tags", Title: "Tags", Count: 10}, + {Area: "left", Kind: "tagcloud", Title: "Tag cloud"}, } { if _, err := bs.CreateModule(ctx, &m); err != nil { return err @@ -107,19 +109,34 @@ func seed(ctx context.Context, cfg *config.Config, st *store.Store) error { for i := 0; i < 10; i++ { body := fmt.Sprintf("Filler post number %d, here to demonstrate pagination.", i+1) post := &store.Post{PageID: home.ID, Title: fmt.Sprintf("Filler post %d", i+1), Slug: fmt.Sprintf("filler-%d", i+1), BodyMD: body, BodyHTML: markdown.Render(body), Published: true} - if _, err := bs.CreatePost(ctx, post); err != nil { + created, err := bs.CreatePost(ctx, post) + if err != nil { + return err + } + tags := []string{"filler"} // uneven use, so the tag cloud has some spread + if i%3 == 0 { + tags = append(tags, "Go") + } + if err := bs.SetPostTags(ctx, created.ID, seedTags(tags...)); err != nil { return err } } - posts := []struct{ page, title, body string }{ - {"home", "Welcome to my corner of the web", "This is the **first post**. It lives on the home page.\n\nThings I plan to write about:\n\n- tomatoes\n- Go\n- the occasional recipe\n\n + ")\n\nThat image above was uploaded through the dashboard, and here is a file to download: [notes.txt](/media/" + notes.ID.String() + ")."}, - {"home", "Why I still like plain HTML", "No frameworks, no build step. Just `<p>` tags and a stylesheet.\n\n> Simplicity is prerequisite for reliability. — Dijkstra\n\n```go\nfunc main() {\n\tfmt.Println(\"hello\")\n}\n```"}, - {"news", "Site is up", "The blog is live. Expect sporadic updates."}, - {"news", "Tomato season", "First ripe tomato of the year! Table for the record:\n\n| Variety | Days |\n|---|---|\n| Cherry | 62 |\n| Beefsteak | 85 |"}, + posts := []struct { + page, title, body string + tags []string + }{ + {"home", "Welcome to my corner of the web", "This is the **first post**. It lives on the home page.\n\nThings I plan to write about:\n\n- tomatoes\n- Go\n- the occasional recipe\n\n + ")\n\nThat image above was uploaded through the dashboard, and here is a file to download: [notes.txt](/media/" + notes.ID.String() + ").", []string{"Go", "tomatoes", "meta"}}, + {"home", "Why I still like plain HTML", "No frameworks, no build step. Just `<p>` tags and a stylesheet.\n\n> Simplicity is prerequisite for reliability. — Dijkstra\n\n```go\nfunc main() {\n\tfmt.Println(\"hello\")\n}\n```", []string{"Go", "web"}}, + {"news", "Site is up", "The blog is live. Expect sporadic updates.", []string{"meta"}}, + {"news", "Tomato season", "First ripe tomato of the year! Table for the record:\n\n| Variety | Days |\n|---|---|\n| Cherry | 62 |\n| Beefsteak | 85 |", []string{"tomatoes", "garden", "Συνταγές"}}, } for _, p := range posts { post := &store.Post{PageID: pages[p.page].ID, Title: p.title, Slug: slug.Make(p.title), BodyMD: p.body, BodyHTML: markdown.Render(p.body), Published: true} - if _, err := bs.CreatePost(ctx, post); err != nil { + created, err := bs.CreatePost(ctx, post) + if err != nil { + return err + } + if err := bs.SetPostTags(ctx, created.ID, seedTags(p.tags...)); err != nil { return err } } @@ -143,3 +160,12 @@ func seed(ctx context.Context, cfg *config.Config, st *store.Store) error { log.Printf("seed: superadmin %q (password %q in dev), blogger alice / alicealice at %s", cfg.SuperadminUsername, "admin", cfg.BlogURL("alice")) return nil } + +// seedTags builds tags the way the post form does: the slug is made from the name. +func seedTags(names ...string) []store.Tag { + out := make([]store.Tag, 0, len(names)) + for _, n := range names { + out = append(out, store.Tag{Name: n, Slug: slug.Make(n)}) + } + return out +} |
