From 4a6f987bf7f9d73e9dd33d225fd2d73e9bf69a88 Mon Sep 17 00:00:00 2001 From: grm Date: Wed, 16 Sep 2026 19:10:13 +0300 Subject: Merge the Layout and Design tabs into one form with a single Save The look and the layout of a blog were split over two tabs that cross-linked each other, and the Layout tab saved every toggle, move and add straight away through a dozen tiny forms. Now one Design tab holds it all, organised by part of the blog (colours & fonts, header, menu, content, side columns, footer, logo & icon) behind a sticky side index, and one Save stores the theme, every module and the menu atomically (BlogStore.SaveDesign). Module settings are edited inline; Discard changes throws the edits away; a validation error shows the form again exactly as it was sent. The image pickers no longer render the whole library four times over: a select of the newest images, and a script-driven panel that fetches the library page by page (GET /files as JSON) with lazy thumbnails. New options: posts per page, site title size, footer alignment. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A --- internal/web/templates/dashboard/design.html | 435 +++++++++++++++++++++++---- 1 file changed, 372 insertions(+), 63 deletions(-) (limited to 'internal/web/templates/dashboard/design.html') diff --git a/internal/web/templates/dashboard/design.html b/internal/web/templates/dashboard/design.html index e961388..261eb69 100644 --- a/internal/web/templates/dashboard/design.html +++ b/internal/web/templates/dashboard/design.html @@ -1,31 +1,44 @@ {{define "title"}}{{t "Design"}} · {{.Blog.Title}}{{end}} {{define "content"}} {{$t := .Data.theme}} +{{$sub := .Blog.Subdomain}}

{{t "Design"}}

-

{{t "Changes are live as soon as you save; refresh"}} {{t "your blog"}} ↗ {{t "to see them."}}

+

{{t "Everything about how your blog looks and what goes where, saved together with the one button at the bottom. Changes are live as soon as you save; refresh"}} {{t "your blog"}} ↗ {{t "to see them."}}

-

{{t "Schemes"}}{{t "Background"}}{{t "Text"}}{{t "Header"}}{{t "Menu"}}{{t "Posts"}}{{t "Footer"}}{{t "Logo & icon"}}

-

{{t "What appears where — header, columns, footer and their modules — is arranged on the"}} {{t "Layout"}} {{t "tab."}}

-
-

{{t "Colour schemes"}}

-

{{t "A quick start: applying a scheme sets every colour below and saves immediately. Images, fonts and layout are kept."}}

-
- - {{range .Data.presets}} {{end}} -
-
- -
- + + -
-

{{t "Background"}}

+

{{t "Page background"}}

- {{template "imagepick" (dict "name" "bg_image" "current" $t.BgImage "sub" .Blog.Subdomain "images" .Data.images "label" (t "Background image"))}} -
+ {{template "imagepick" (dict "name" "bg_image" "current" $t.BgImage "sub" $sub "images" .Data.images "names" .Data.imageNames "label" (t "Background image"))}} -
-

{{t "Text & content area"}}

+

{{t "Text & links"}}

- +
+ +

{{t "Fonts"}}

-
-
- - -
-

{{t "The content spans the whole page; how much of it the posts get is set by the column widths on the"}} {{t "Layout"}} {{t "tab."}}

-
-

{{t "Posts & dates"}}

+
+

{{t "Content"}}

+

{{t "The box your posts and pages are shown in. It spans whatever the side columns leave."}}

+
+ + + +
+

{{t "Posts"}}

+
+

{{t "Around the posts"}}

+

{{t "Your own HTML before and after the posts — an intro box, a newsletter form, a badge…"}}

+ {{template "arealist" (dict "area" "above" "root" .)}} + {{template "arealist" (dict "area" "below" "root" .)}} +
+ +
+

{{t "Side columns"}}

+
+
{{t "Header"}}
+
+
{{t "Left"}} {{$t.LeftWidth}}%
+
{{t "Main content"}} {{$t.MainWidth}}%
+
{{t "Right"}} {{$t.RightWidth}}%
+
+
{{t "Footer"}}
+
+
+ + +
+

{{t "Between 15% and 40% each; the main content takes the rest. A column only appears when it has at least one module; without columns the posts take the whole width. On phones the columns stack: main content first, then the left and right columns, then the footer."}}

+ +

{{t "Left column"}}

+ + {{template "arealist" (dict "area" "left" "root" . "hint" (t "A narrow column beside your posts. Good for the menu, an archive or recent posts."))}} +

{{t "Right column"}}

+ + {{template "arealist" (dict "area" "right" "root" . "hint" (t "A narrow column on the other side of your posts. Same choices as the left one."))}}
- - {{t "View blog"}} ↗ + + {{t "Discard changes"}} + + {{t "View blog"}} ↗
+ +{{/* Row templates the script clones: one per area and kind, and the two menu entries. */}} +{{range .Data.templates}} +{{end}} +
{{t "Reset to defaults"}}

{{t "Puts every colour, font and layout option back to how a new blog starts and forgets the chosen images (they stay in your library)."}}

-
+
+
+ +{{/* The library panel: filled by the script from /files?kind=image as JSON, one page at a time. */}} + + + {{end}} + +{{/* One area's modules: the list, its empty note and the "add" select. */}} +{{define "arealist"}}{{$r := .root}}{{$area := .area}}{{$rows := index $r.Data.rows $area}}{{$single := index $r.Data.single $area}} +
+ {{if $single}}

{{t (index $r.Data.areaNames $area)}}

{{end}} + {{with .hint}}

{{.}}

{{end}} +
    + {{range $rows}}{{template "modrow" (dict "i" .I "m" .M "pos" .Pos "root" $r)}}{{end}} +
+

{{if $single}}{{t "Nothing extra."}}{{else if eq $area "header"}}{{t "Nothing here yet."}}{{else}}{{t "Nothing here yet — this area is not shown until you add a module."}}{{end}}

+
+ +
+
{{end}} + +{{/* One module row. .i is the field index (or __I__ in a template), .m the module. */}} +{{define "modrow"}}{{$i := .i}}{{$m := .m}}{{$r := .root}} +
  • + + + {{t (index $r.Data.names $m.Kind)}} + {{if and (eq $m.Kind "logo") (not $r.Data.theme.Logo)}}{{t "no logo chosen"}} {{t "pick one"}}{{end}} + + {{if modsettings $m}}
    + {{t "Settings"}} {{modsummary $m}} + {{if or (ne $m.Kind "html") (and (ne $m.Area "above") (ne $m.Area "below"))}}{{if ne $m.Kind "text"}} + + {{end}}{{end}} + {{if eq $m.Kind "recent"}} +
    +

    {{t "The newest published posts from every page of your blog, newest first."}}

    + {{else if eq $m.Kind "archive"}} +

    {{t "Every published post grouped by year and month, newest first. Years and months fold open and shut; the latest year starts open."}}

    + {{else if eq $m.Kind "sitemap"}} +

    {{t "Links to every page of your blog."}}

    + {{else if eq $m.Kind "tags"}} +
    +

    {{t "Your tags with the number of posts in each, most used first. Only published posts count."}}

    + {{else if eq $m.Kind "tagcloud"}} +

    {{t "Every tag in alphabetical order, bigger the more posts use it."}}

    + {{else if eq $m.Kind "search"}} +

    {{t "A box that searches the published posts of every page by title and text; the results open on your blog’s search page. Case does not matter, and several words find posts that contain them in that order, with anything in between."}}

    + {{else if eq $m.Kind "html"}} + +

    {{t "Put into your page exactly as written — embeds, badges and scripts all work. Anything broken here (an unclosed tag, say) can break the page around it, so check your blog after saving. 20 KB at most."}}

    + {{else if eq $m.Kind "text"}} + +

    {{t "Plain text; each line becomes its own paragraph."}}

    + {{end}} +
    {{end}} +
  • {{end}} + +{{/* One menu entry: a page (fixed) or a link (text and address editable). */}} +{{define "menurow"}}{{$i := .i}}{{$it := .item}} +{{end}} -- cgit v1.2.3