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 /internal/web/layout.go | |
| 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 'internal/web/layout.go')
| -rw-r--r-- | internal/web/layout.go | 41 |
1 files changed, 29 insertions, 12 deletions
diff --git a/internal/web/layout.go b/internal/web/layout.go index ad55e82..b8069a0 100644 --- a/internal/web/layout.go +++ b/internal/web/layout.go @@ -13,8 +13,8 @@ import ( // blocks; the side columns take the small boxes; the footer its own set. var moduleKinds = map[string][]string{ "header": {"title", "logo", "menu"}, - "left": {"menu", "recent", "archive", "logo", "html"}, - "right": {"menu", "recent", "archive", "logo", "html"}, + "left": {"menu", "recent", "archive", "tags", "tagcloud", "logo", "html"}, + "right": {"menu", "recent", "archive", "tags", "tagcloud", "logo", "html"}, "above": {"html"}, "below": {"html"}, "footer": {"text", "rss", "sitemap", "html"}, @@ -22,15 +22,17 @@ var moduleKinds = map[string][]string{ // Friendly names for the dashboard (English; translate with t / s.tr at the point of use). var moduleNames = map[string]string{ - "title": "Blog title & tagline", - "logo": "Logo", - "menu": "Menu", - "archive": "Archive (posts by month)", - "recent": "Recent posts", - "html": "Custom HTML", - "rss": "RSS link", - "text": "Footer text", - "sitemap": "Site map", + "title": "Blog title & tagline", + "logo": "Logo", + "menu": "Menu", + "archive": "Archive (posts by month)", + "recent": "Recent posts", + "tags": "Tags", + "tagcloud": "Tag cloud", + "html": "Custom HTML", + "rss": "RSS link", + "text": "Footer text", + "sitemap": "Site map", } var areaNames = map[string]string{ @@ -53,7 +55,7 @@ func allowedKind(area, kind string) bool { // moduleHasSettings says whether the module has an edit page. func moduleHasSettings(kind string) bool { switch kind { - case "html", "text", "recent", "archive", "sitemap": + case "html", "text", "recent", "archive", "sitemap", "tags", "tagcloud": return true } return false @@ -64,6 +66,11 @@ func moduleSummary(lang string, m store.Module) string { switch m.Kind { case "recent": return i18n.Tf(lang, "%d posts", m.Count) + headingNote(lang, m) + case "tags": + if m.Count == 0 { + return i18n.T(lang, "all tags") + headingNote(lang, m) + } + return i18n.Tf(lang, "%d tags", m.Count) + headingNote(lang, m) case "html", "text": body := strings.Join(strings.Fields(m.Body), " ") if body == "" { @@ -160,6 +167,16 @@ func (l Layout) NeedsArchive() bool { return false } +// NeedsTags says whether any visible module shows the tags. +func (l Layout) NeedsTags() bool { + for _, m := range l.all() { + if m.Kind == "tags" || m.Kind == "tagcloud" { + return true + } + } + return false +} + // MaxRecent is the longest "recent posts" list any visible module wants (0 = none). func (l Layout) MaxRecent() int { n := 0 |
