aboutsummaryrefslogtreecommitdiffstats
path: root/AGENTS.md
diff options
context:
space:
mode:
Diffstat (limited to 'AGENTS.md')
-rw-r--r--AGENTS.md64
1 files changed, 47 insertions, 17 deletions
diff --git a/AGENTS.md b/AGENTS.md
index 1ea5ef9..2560696 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -39,7 +39,7 @@ table and deployment notes.
- Changes are live immediately — there is no draft/preview system. The UX
is "save, then refresh your blog tab"; keep the "View blog ↗" links.
- Theme customisation is a structured form only; **no custom CSS input**.
- The one escape hatch is the **custom HTML module** (Layout tab), which the
+ The one escape hatch is the **custom HTML module** (Design tab), which the
owner chose to store and output **unsanitised**. The blast radius is the
blogger's own origin: the session cookie is host-only on the base domain
and `HttpOnly`, management POSTs need the HMAC `_csrf` token, and the
@@ -63,8 +63,9 @@ internal/i18n/ languages, T/Tf (English keys → catalog), FormatDate/Mon
el.go is the Greek catalog, el_months.go the month tables
internal/slug/ Make/Valid/WithSuffix (Greek transliteration included)
internal/web/ server.go (host router, middleware, render helpers)
- routes.go (all routes), handlers_*.go (sections = announcements, layout = modules + menu,
- files = the upload library), filetype.go (what an upload is, how it may be served)
+ routes.go (all routes), handlers_*.go (sections = announcements, design = the one
+ look-and-layout form, files = the upload library), design_form.go (parsing the
+ design form's module and menu rows), filetype.go (what an upload is, how it may be served)
theme.go (colours/fonts/layout switches), layout.go (module kinds, archive grouping)
templates/ (embedded; layouts/, partials/, auth/, dashboard/, admin/, blog/)
static/ (dashboard.css — lean look; custom properties, flexbox and grid
@@ -136,28 +137,57 @@ internal/web/ server.go (host router, middleware, render helpers)
Flash messages travel as `?ok=` query param via `redirectOK` (a `#anchor`
in the target is kept after the query). Add new
templates to `TestAllTemplatesParse`.
-- **Theme** (`web/theme.go`): struct stored as jsonb on `blogs.theme`.
+- **Design tab** (`handlers_design.go`, `dashboard/design.html`,
+ `GET|POST /b/{sub}/design`): the whole look and layout of a blog is **one
+ form with one Save** — theme values, the area switches and column widths,
+ the modules of every area (with their settings inline, folded in a
+ `<details>`) and the menu — stored atomically by `BlogStore.SaveDesign`
+ (theme + full module list + full menu in one transaction: rows with an id
+ are updated, without one inserted, missing ones deleted). Sections are cards
+ with ids (`designSections`) a sticky side index jumps to; the hidden `at`
+ field says which one was in view so the flash returns there. Module rows are
+ `mod.<i>.{id,area,kind,title,body,count,pos,del}` and menu rows
+ `menu.<i>.{id,page,label,url,pos,del}` (`design_form.go`: `parseModules`,
+ `parseMenu`); `i` only tells rows apart, `pos` orders them. The inline
+ script reorders rows in the DOM (renumbering `pos` on submit), clones the
+ `<template id="tpl-<area>-<kind>">` rows to add modules, fills the colour
+ inputs from a scheme button, tracks unsaved changes (badge, `beforeunload`,
+ confirm on "Discard changes") — all of it optional: without it `pos` is a
+ number box, Remove a checkbox, `add.<area>` / `menu_add_page` /
+ `menu_add_label`+`url` / the `preset` select are applied on save. A
+ validation error re-renders the form **with what was sent** (the parsers
+ return every row along with the error; a bad link stays as a row to fix).
+ `POST /b/{sub}/design/reset` stores `DefaultTheme()` and calls
+ `ResetModules`. `GET /b/{sub}/layout` (the old tab) redirects to
+ `/design#columns`.
+- **Theme** (`web/theme.go`): struct stored as jsonb on `settings.theme`.
`ParseTheme` merges over `DefaultTheme()` and `normalize()` clamps every
value to an allowlist (hex colours, enum strings, uuid image ids, column
percentages) — this is what makes inlining the generated CSS safe. Add
new options in the struct, `normalize`, `ThemeFromForm`, the CSS template
and `dashboard/design.html`; defaults must reproduce the look blogs had
before the option existed. Non-CSS options (`ShowDates`, `DateFormat` via
- `FormatDate`, `Favicon`, `Logo`) are read by the blog templates.
+ `FormatDate`, `PostsPerPage`, `Favicon`, `Logo`) are read by the blog
+ templates and handlers. `TitleSize` is emitted inside the wide-screen media
+ query so blog.css's phone rule keeps winning.
Optional colours (`LinkHover`, `NavHover`) are `""` = inherit and come
from a colour input paired with a `<name>_custom` checkbox, shown/hidden
- with CSS only (`.hoverpick > input:not(:checked) ~ …`). Image fields
- use the `imagepick` partial's radios (the library's `kind = image` files,
- `ListFiles(ctx, "image", "", 0, 0)`): `none` clears, a uuid selects, no
- value keeps (`pickImage`); an upload in `<name>_file` wins.
- `Presets()`/`WithPreset` are the colour schemes (`POST /b/{sub}/design/preset`,
- colours only); `POST /b/{sub}/design/reset` stores `DefaultTheme()` and
- calls `ResetModules`.
- The layout switches (`HeaderOn/LeftOn/RightOn/FooterOn`, `LeftWidth`,
- `RightWidth`, `KeepColumns`) also live in the theme but are edited from the Layout tab
- (`LayoutFromForm`, `SetAreaOn`); `ThemeFromForm` never touches them.
+ with CSS only (`.hoverpick > input:not(:checked) ~ …`). Image fields use the
+ `imagepick` partial: a `<select>` of the `recentImages` newest library
+ images (`kind = image`) plus the chosen one (`imageNames` looks up an older
+ one's filename), so the page never grows with the library; `none` clears, a
+ uuid selects, no value keeps (`pickImage`); an upload in `<name>_file` wins.
+ With the script the select is hidden and "Choose from library…" opens one
+ shared panel that fetches `GET /b/{sub}/files?kind=image&q=&p=` with
+ `Accept: application/json` (`{files:[{id,filename}], page, last}`, 50 a
+ page, thumbnails `loading=lazy`) and writes the choice into the select.
+ `Presets()`/`WithPreset` are the colour schemes (colours only; `Preset.Colors`
+ is the JSON the scheme buttons carry). The layout switches
+ (`HeaderOn/LeftOn/RightOn/FooterOn`, `LeftWidth`, `RightWidth`,
+ `KeepColumns`) live in the theme and are read by `ThemeFromForm` like
+ everything else (checkboxes: unticked = off).
- **Layout** (`modules` table, `store/modules.go`, `web/layout.go`,
- `handlers_layout.go`, `/b/{sub}/layout…`): a blog page is six areas —
+ `design_form.go`): a blog page is six areas —
`header`, `left`, `above`, `below` (the two custom-HTML slots inside the
main column, one module at most), `right`, `footer` — each an ordered
list of modules (`kind`: title, logo, menu, search, archive, recent, tags,
@@ -185,7 +215,7 @@ internal/web/ server.go (host router, middleware, render helpers)
on delete) and custom links (`label` + `url`, validated by
`validLinkURL`: http(s), mailto or a `/path`). `Page.ShowInNav` is
derived (`EXISTS` on `menu_items`); `CreatePage`/`UpdatePage` add or
- remove the entry so the page form's checkbox and the Layout tab agree.
+ remove the entry so the page form's checkbox and the Design tab agree.
- **Favicon**: `static/favicon.svg` + `favicon.png` are the defaults, linked
from both layouts. A blog can set `theme.favicon` (an image id) which the
blog layout links instead. `GET /favicon.ico` on every host