diff options
Diffstat (limited to 'AGENTS.md')
| -rw-r--r-- | AGENTS.md | 64 |
1 files changed, 47 insertions, 17 deletions
@@ -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 |
