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 ++++++++++++++++++--- internal/web/templates/dashboard/layout.html | 113 ------ internal/web/templates/dashboard/module_form.html | 41 -- internal/web/templates/dashboard/overview.html | 2 +- internal/web/templates/dashboard/pages.html | 2 +- internal/web/templates/dashboard/section_form.html | 2 +- 6 files changed, 375 insertions(+), 220 deletions(-) delete mode 100644 internal/web/templates/dashboard/layout.html delete mode 100644 internal/web/templates/dashboard/module_form.html (limited to 'internal/web/templates/dashboard') 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}} diff --git a/internal/web/templates/dashboard/layout.html b/internal/web/templates/dashboard/layout.html deleted file mode 100644 index 5c9332a..0000000 --- a/internal/web/templates/dashboard/layout.html +++ /dev/null @@ -1,113 +0,0 @@ -{{define "title"}}{{t "Layout"}} · {{.Blog.Title}}{{end}} -{{define "content"}} -{{$t := .Data.theme}} -{{$u := print "/b/" .Blog.Subdomain "/layout"}} -
    -

    {{t "Layout"}}

    -

    {{t "Decide what goes where on your blog. Every change here is saved straight away; refresh"}} {{t "your blog"}} ↗ {{t "to see it."}} {{t "Colours and fonts are on the"}} {{t "Design"}} {{t "tab."}}

    -
    -

    {{t "Columns"}}{{t "Header"}}{{t "Left column"}}{{t "Main content"}}{{t "Right column"}}{{t "Footer"}}{{t "Menu"}}

    - -
    -

    {{t "Columns"}}

    -
    -
    {{t "Header"}}{{if not $t.HeaderOn}} ({{t "hidden"}}){{end}}
    -
    -
    {{t "Left"}} {{$t.LeftWidth}}%
    -
    {{t "Main content"}} {{$t.MainWidth}}%
    -
    {{t "Right"}} {{$t.RightWidth}}%
    -
    -
    {{t "Footer"}}{{if not $t.FooterOn}} ({{t "hidden"}}){{end}}
    -
    -
    - -
    - - -
    -

    {{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."}}

    - -

    -
    -
    - -{{template "area" (dict "area" "header" "root" . "hint" (t "The top of every page. Stack the title, a logo and the menu in the order you like. A banner image can be chosen on the Design tab."))}} -{{template "area" (dict "area" "left" "root" . "hint" (t "A narrow column beside your posts. Good for the menu, an archive or recent posts."))}} - -
    -

    {{t "Main content"}}

    -

    {{t "Your posts and pages always show here. You can add your own HTML before and after them — an intro box, a newsletter form, a badge…"}}

    - - {{range $area := .Data.areaOrder}}{{$mods := index $.Data.areas $area}} - - {{if $mods}}{{$m := index $mods 0}} - - - {{else}} - - - {{end}} - {{end}} -
    {{t (index $.Data.areaNames $area)}}{{t (index $.Data.names $m.Kind)}}{{with modsummary $m}} — {{.}}{{end}}{{t "edit"}} {{t "remove"}}{{t "Nothing extra."}}
    -
    - -{{template "area" (dict "area" "right" "root" . "hint" (t "A narrow column on the other side of your posts. Same choices as the left one."))}} -{{template "area" (dict "area" "footer" "root" . "hint" (t "The bottom of every page: a copyright line, the RSS link, a list of your pages."))}} - - -{{end}} - -{{/* One area card: on/off switch, its modules in order, and an "add" form. */}} -{{define "area"}}{{$r := .root}}{{$area := .area}}{{$u := print "/b/" $r.Blog.Subdomain "/layout"}}{{$on := $r.Data.theme.AreaOn $area}}{{$mods := index $r.Data.areas $area}} -
    -
    -

    {{t (index $r.Data.areaNames $area)}}{{if not $on}} {{t "hidden"}}{{end}}

    -
    -
    -

    {{.hint}}

    - {{if $mods}} - {{range $mods}} - - - - {{end}} -
    -
    -
    -
    {{t (index $r.Data.names .Kind)}}{{with modsummary .}} — {{.}}{{end}} - {{if and (eq .Kind "logo") (not $r.Data.theme.Logo)}}{{t "no logo chosen"}} {{t "pick one on the"}} {{t "Design"}} {{t "tab."}}{{end}}{{if modsettings .}}{{t "edit"}} {{end}}{{t "remove"}}
    {{else}}

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

    {{end}} -
    - - - -
    -
    {{end}} diff --git a/internal/web/templates/dashboard/module_form.html b/internal/web/templates/dashboard/module_form.html deleted file mode 100644 index a0fe95b..0000000 --- a/internal/web/templates/dashboard/module_form.html +++ /dev/null @@ -1,41 +0,0 @@ -{{define "title"}}{{.Data.name}} · {{.Blog.Title}}{{end}} -{{define "content"}} -{{$m := .Data.module}} -
    -

    {{.Data.name}} {{tf "in the %s" (lower .Data.area)}}

    -
    - - {{if or (eq $m.Kind "recent") (eq $m.Kind "archive") (eq $m.Kind "sitemap") (eq $m.Kind "tags") (eq $m.Kind "tagcloud") (eq $m.Kind "search") (and (eq $m.Kind "html") (or (eq $m.Area "left") (eq $m.Area "right") (eq $m.Area "footer")))}} - - {{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}} -

    - - {{t "View blog"}} ↗ - {{t "Back to layout"}} -

    -
    -
    -{{end}} diff --git a/internal/web/templates/dashboard/overview.html b/internal/web/templates/dashboard/overview.html index 0f611cd..e239445 100644 --- a/internal/web/templates/dashboard/overview.html +++ b/internal/web/templates/dashboard/overview.html @@ -7,7 +7,7 @@

    {{t "Pages"}}

    diff --git a/internal/web/templates/dashboard/pages.html b/internal/web/templates/dashboard/pages.html index cb40eb4..6c3b56d 100644 --- a/internal/web/templates/dashboard/pages.html +++ b/internal/web/templates/dashboard/pages.html @@ -3,7 +3,7 @@

    {{t "Pages"}}

    -

    {{t "Each page holds its own list of posts. Which pages are in the menu, and in what order, is set on the"}} {{t "Layout"}} {{t "tab."}}

    +

    {{t "Each page holds its own list of posts. Which pages are in the menu, and in what order, is set on the"}} {{t "Design"}} {{t "tab."}}

    diff --git a/internal/web/templates/dashboard/section_form.html b/internal/web/templates/dashboard/section_form.html index d5b4d25..0171aaa 100644 --- a/internal/web/templates/dashboard/section_form.html +++ b/internal/web/templates/dashboard/section_form.html @@ -22,7 +22,7 @@ -

    {{t "“Top” of a side column means above its modules, “bottom” below them. If that column is not shown on your blog (see"}} {{t "Layout"}}) {{t "the announcement moves to the main content instead."}}

    +

    {{t "“Top” of a side column means above its modules, “bottom” below them. If that column is not shown on your blog (see"}} {{t "Design"}}) {{t "the announcement moves to the main content instead."}}

    {{template "editor" (dict "name" "body" "value" $s.BodyMD "format" $s.Format "rows" 10 "tall" false "upload" (print "/b/" .Blog.Subdomain "/files/upload") "preview" (print "/b/" .Blog.Subdomain "/preview") "csrf" .CSRF)}} -- cgit v1.2.3