aboutsummaryrefslogtreecommitdiffstats
path: root/AGENTS.md
diff options
context:
space:
mode:
Diffstat (limited to 'AGENTS.md')
-rw-r--r--AGENTS.md53
1 files changed, 43 insertions, 10 deletions
diff --git a/AGENTS.md b/AGENTS.md
index 2560696..d518f8a 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -66,7 +66,8 @@ internal/web/ server.go (host router, middleware, render helpers)
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)
+ theme.go (colours/fonts/layout switches), layout.go (module kinds, archive grouping),
+ summary.go (what a listing shows of a post)
templates/ (embedded; layouts/, partials/, auth/, dashboard/, admin/, blog/)
static/ (dashboard.css — lean look; custom properties, flexbox and grid
are fine there. blog.css — floats except the three-column `.cols`,
@@ -168,8 +169,10 @@ internal/web/ server.go (host router, middleware, render helpers)
and `dashboard/design.html`; defaults must reproduce the look blogs had
before the option existed. Non-CSS options (`ShowDates`, `DateFormat` via
`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.
+ templates and handlers, and so are `ListStyle` (`full` | `summary`, see
+ Summaries) and `PostImage` (where the post page shows the featured image:
+ `top` | `bottom` | `list` = listings only). `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
@@ -177,10 +180,14 @@ internal/web/ server.go (host router, middleware, render helpers)
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
+ The `imagelib` partial (one per form; the design page and the post form
+ both include it) is the panel behind "Choose from library…", with its own
+ script: it marks every `.imagepick` with `.js` (hiding the select, showing
+ the buttons — `.ip-nojs`/`.ip-jsonly`, independent of the form's own `js`
+ class), 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.
+ page, thumbnails `loading=lazy`), writes the choice into the select and
+ fires `change` on it, which is how the design form learns it is dirty.
`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`,
@@ -255,8 +262,10 @@ internal/web/ server.go (host router, middleware, render helpers)
10), set at `POST /admin/blogs/{id}/upload-limit` from `admin/index.html`;
`Blog.UploadLimit(cfg)` resolves it.
- **Editor** (`partials/editor.html`, args via `dict`: name, value, format,
- rows, tall, upload, preview, csrf): a Format row (radios `format` =
- `markdown`|`html`, always rendered) and a textarea with a Markdown toolbar
+ rows, tall, upload, preview, csrf, and `formatof` for a second editor in
+ the same form — the page's outro passes `"intro"` — which then renders no
+ Format row and watches the named editor's radios): a Format row (radios
+ `format` = `markdown`|`html`) and a textarea with a Markdown toolbar
(bold, italic, strike, heading cycle, quote, code, lists, rule, link box,
Insert file), Ctrl+B/I/K, list continuation on Enter, and a Write/Preview
toggle that POSTs the text to `/b/{sub}/preview` (`handlePreview`:
@@ -266,7 +275,8 @@ internal/web/ server.go (host router, middleware, render helpers)
+ "Insert file" + cheat-sheet, and paste/drop take any file. Forms using it
must be `multipart/form-data` and their save handler must set `Format` with
`pick(r.FormValue("format"), store.FormatMarkdown, store.FormatHTML)`, call
- `s.readUpload(r, "inline_file", false)` + `appendFile(body, f, format)`
+ `s.readUpload(r, "<name>_file", false)` (`body_file`, `intro_file`,
+ `outro_file`) + `appendFile(body, f, format)`
(the no-JS path: `fileMarkdown` or `fileHTML`, i.e. `<img>`/`<a>`), and
store `renderBody(format, src)` (`markdown.Render`, or the source untouched
for HTML) in the `*_html` column — the public templates only ever print
@@ -279,6 +289,28 @@ internal/web/ server.go (host router, middleware, render helpers)
scripts — because the text may not be the viewer's own (a superadmin edits
other people's blogs) and must never run on the dashboard origin, where the
session lives. Markdown preview keeps using `.ed-preview` in the page.
+- **Page intro and outro** (`pages.intro_md/intro_html`, `outro_md/outro_html`,
+ one `format` for both): text before and after the page's posts, the
+ blog-wide `above`/`below` HTML modules' per-page counterpart (a home page
+ with a site map or a widget after its posts). `blog/page.html` prints the
+ outro after the `postlist` and treats a page with only an outro as not empty.
+- **Summaries** (`web/summary.go`, `theme.list_style`): with `summary` a
+ listing (page or tag) shows `Post.Excerpt` + a "Read more" link when
+ `Post.HasMore` — both filled by `fillExcerpts` in the listing handlers, never
+ stored. `postSummary` cuts the *source* at `<!--more-->` and renders that
+ part (bluemonday drops the comment from the full body; in HTML mode it
+ stays, harmless); without the marker a Markdown post is cut at the first
+ blank-line block boundary past `summaryWords` (70) words, never inside a
+ code fence, and an HTML post is shown whole (cutting hand-written markup
+ blind would leave tags open). `full` puts the whole body in `Excerpt`.
+- **Featured image** (`posts.image uuid REFERENCES files ON DELETE SET
+ NULL`, `Post.Image` as a string id or ""): picked on the post form with the
+ `imagepick` partial (`image`, upload `image_file` wins; `postFormData`
+ loads the recent images like the design page and clears an id whose file
+ is gone). Listings float it right as `.post-thumb` (`has-thumb` clears the
+ float); the post page shows it as `.post-image` above or below the body per
+ `theme.post_image`, or not at all (`list`). No resizing: the thumbnail is
+ the file scaled by CSS.
- **Announcements** (`sections` table, `store/sections.go`,
`handlers_sections.go`, `/b/{sub}/announcements…`): per-blog notices with
`placement` (`<column>-<position>`: left|main|right × top|bottom, split by
@@ -390,7 +422,8 @@ superadmin password to `admin`. Production refuses both.
`internal/db/migrations/control/` for users and the registry; goose
`-- +goose Up/Down` sections; they run automatically at startup. Never
edit an applied migration. Blog chain so far: `00001_init`, `00002_language`,
- `00003_files`, `00004_tags`, `00005_search`, `00006_format`; control: `00001_init`, `00002_upload_limit`. Both chains were re-baselined at 00001 after
+ `00003_files`, `00004_tags`, `00005_search`, `00006_format`, `00007_post_image`,
+ `00008_page_outro`; control: `00001_init`, `00002_upload_limit`. Both chains were re-baselined at 00001 after
the move to per-blog databases; deployments from before it have
`goose_db_version` rows 2–7 in the control DB that must be deleted once
(README "Upgrading from a single database") or the next control migration