aboutsummaryrefslogtreecommitdiffstats
path: root/internal/web/handlers_blog.go
diff options
context:
space:
mode:
authorgrm <grm@eyesin.space>2026-09-13 00:38:24 +0300
committergrm <grm@eyesin.space>2026-09-13 00:38:24 +0300
commitd11e4c9544f544b3a024c7ac705a674b4096cf50 (patch)
treea60be98a37b764dfcb655b2738c8a991a248103b /internal/web/handlers_blog.go
parente974eb6f3feb06c8951f85c1d34161938905f807 (diff)
downloadblogspace-d11e4c9544f544b3a024c7ac705a674b4096cf50.tar.gz
blogspace-d11e4c9544f544b3a024c7ac705a674b4096cf50.tar.bz2
blogspace-d11e4c9544f544b3a024c7ac705a674b4096cf50.zip
Add the Layout tab: five areas with modules, columns and a menu editor
A blog page is now header, left column, main content, right column and footer, each holding an ordered list of modules (blog title, logo, menu, archive by year/month, recent posts, custom HTML, footer text, RSS link, site map). Side columns have percentage widths, can be hidden, and can keep their space when empty; on phones they stack under the posts. The menu becomes its own ordered list mixing pages and outside links. Announcements are placed per column at its top or bottom. Modules and menu items get tables; the migration derives them from each blog's old theme (nav position, footer text, show-in-nav pages) so existing blogs look the same. Custom HTML is deliberately stored and served as-is (owner's decision; see AGENTS.md for why the blast radius is the blogger's own origin). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
Diffstat (limited to 'internal/web/handlers_blog.go')
-rw-r--r--internal/web/handlers_blog.go51
1 files changed, 28 insertions, 23 deletions
diff --git a/internal/web/handlers_blog.go b/internal/web/handlers_blog.go
index 9b2fe5e..42ec45b 100644
--- a/internal/web/handlers_blog.go
+++ b/internal/web/handlers_blog.go
@@ -12,42 +12,47 @@ import (
const postsPerPage = 10
-// blogView gathers what every public page needs: theme css, nav, announcements, the blog.
+// blogView gathers what every public page needs: theme css, the layout with
+// its modules and their data, the menu, announcements, the blog.
func (s *Server) blogView(r *http.Request) (map[string]any, error) {
blog := currentBlog(r)
- pages, err := s.st.ListPages(r.Context(), blog.ID)
+ ctx := r.Context()
+ pages, err := s.st.ListPages(ctx, blog.ID)
if err != nil {
return nil, err
}
- sections, err := s.st.EnabledSections(r.Context(), blog.ID)
+ menu, err := s.st.ListMenu(ctx, blog.ID)
+ if err != nil {
+ return nil, err
+ }
+ mods, err := s.st.ListModules(ctx, blog.ID)
+ if err != nil {
+ return nil, err
+ }
+ sections, err := s.st.EnabledSections(ctx, blog.ID)
if err != nil {
return nil, err
}
theme := ParseTheme(blog.ThemeJSON)
- var nav []store.Page
- for _, p := range pages {
- if p.ShowInNav && (theme.NavShowHome || !p.IsHome) {
- nav = append(nav, p)
+ layout := buildLayout(theme, mods)
+ v := map[string]any{"theme": theme, "css": theme.CSS(), "layout": layout, "menu": menu, "pages": pages}
+ // Module data is only fetched when a visible module shows it.
+ if layout.NeedsArchive() {
+ index, err := s.st.PublishedPostIndex(ctx, blog.ID)
+ if err != nil {
+ return nil, err
}
+ v["archive"] = groupArchive(index)
}
- above, below, sidebar := splitSections(sections, theme.NavPosition == "left-sidebar")
- return map[string]any{"theme": theme, "css": theme.CSS(), "nav": nav, "above": above, "below": below, "sidebar": sidebar}, nil
-}
-
-// splitSections groups announcements by placement. The sidebar only exists in
-// the left-sidebar layout; elsewhere sidebar announcements go above the content.
-func splitSections(secs []store.Section, hasSidebar bool) (above, below, sidebar []store.Section) {
- for _, sec := range secs {
- switch {
- case sec.Placement == "below":
- below = append(below, sec)
- case sec.Placement == "sidebar" && hasSidebar:
- sidebar = append(sidebar, sec)
- default:
- above = append(above, sec)
+ if n := layout.MaxRecent(); n > 0 {
+ recent, err := s.st.RecentPublishedPosts(ctx, blog.ID, n)
+ if err != nil {
+ return nil, err
}
+ v["recent"] = recent
}
- return
+ v["notices"] = placeNotices(sections, layout)
+ return v, nil
}
func (s *Server) handleBlogHome(w http.ResponseWriter, r *http.Request) {