diff options
| author | grm <grm@eyesin.space> | 2026-09-16 18:37:20 +0300 |
|---|---|---|
| committer | grm <grm@eyesin.space> | 2026-09-16 18:37:20 +0300 |
| commit | 6b6b5d2e35ff182a0732da245f4eb37c8afa0564 (patch) | |
| tree | f56960ae86c3c289f9a68e38ec01f1e3ff997466 /AGENTS.md | |
| parent | 5119018feeaa22c47c0e91e15d3b9414dd6e0772 (diff) | |
| download | blogspace-6b6b5d2e35ff182a0732da245f4eb37c8afa0564.tar.gz blogspace-6b6b5d2e35ff182a0732da245f4eb37c8afa0564.tar.bz2 blogspace-6b6b5d2e35ff182a0732da245f4eb37c8afa0564.zip | |
Add an HTML mode to posts, page intros and announcements
Markdown is the default and unchanged; the editor's new Format switch
stores the text as raw HTML instead, put on the blog exactly as written.
Like the custom HTML module it is unsanitised on purpose: the escape
hatch for embeds, scripts and inline styles that Markdown cannot express.
The source column keeps holding the text in both modes and a `format`
column says how to read it, so the public templates and the feed still
print the stored `*_html`. The dashboard preview of HTML goes into a
sandboxed iframe rather than the page, because a superadmin edits other
people's blogs and their markup must never run on the dashboard origin.
Search snippets of HTML posts are cut from a tag-stripped copy.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
Diffstat (limited to 'AGENTS.md')
| -rw-r--r-- | AGENTS.md | 52 |
1 files changed, 35 insertions, 17 deletions
@@ -28,10 +28,14 @@ table and deployment notes. **Each blog is its own database** (`blog_<sub>`, see Key mechanics) so a blog is backed up and restored with plain `pg_dump`/`psql`; the control database (`DATABASE_URL`) holds only `users` and the `blogs` registry. -- Posts are Markdown (goldmark → bluemonday). No WYSIWYG. The editor's - toolbar, preview and "Insert file" are scripted conveniences over a plain - textarea (`partials/editor.html`); the no-JS fallback (bare textarea, file - appended on save) must keep working. +- Posts, page intros and announcements are Markdown (goldmark → bluemonday) + **or raw HTML**: each has a `format` column (`markdown` | `html`), the + editor's Format radios. HTML is stored and output **unsanitised** — the + owner's decision, same as the custom HTML module below, same blast radius. + No WYSIWYG. The editor's toolbar, preview and "Insert file" are scripted + conveniences over a plain textarea (`partials/editor.html`); the no-JS + fallback (bare textarea, format radios, file appended on save) must keep + working. - 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**. @@ -212,7 +216,7 @@ internal/web/ server.go (host router, middleware, render helpers) `` for images and `[name](…)` for the rest. Deleting a file clears theme references to it. `POST /b/{sub}/files/upload` takes several `file` parts (the no-JS `<input multiple>`), or answers JSON - (`{id, filename, kind, size, markdown}` / `{error}`) for one file when the + (`{id, filename, kind, size, markdown, html}` / `{error}`) for one file when the request has `Accept: application/json` — what the editor and the Files page scripts call. `dashboard/files.html` lists by `?kind=&q=&p=` (`ListFiles`, 50 per page, `pageBounds` clamps), shows `FileUsage` and the @@ -220,17 +224,31 @@ internal/web/ server.go (host router, middleware, render helpers) is `blogs.max_upload_bytes` in the control DB (NULL = `MAX_UPLOAD_MB`, now 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, rows, - tall, upload, preview, csrf): 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`: `markdown.Render` - → `{html}`, nothing stored). Every edit goes through one `replace()` built on - `execCommand("insertText")` so browser undo works. The toolbar is `hidden` - until the script runs; without JS it is a bare textarea + "Insert file" + - cheat-sheet, and paste/drop take any file. Forms using it must be - `multipart/form-data` and their save handler must call - `s.readUpload(r, "inline_file", false)` + `appendFileMD` (the no-JS path). +- **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 + (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`: + `markdown.Render` → `{html}`, nothing stored). Every edit goes through one + `replace()` built on `execCommand("insertText")` so browser undo works. The + toolbar is `hidden` until the script runs; without JS it is a bare textarea + + "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)` + (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 + that column. + **HTML mode** (`.editor.html`, toggled by the radios): the Markdown toolbar + groups (`.ed-md`), link box, shortcuts, list continuation and cheat-sheet + are off, the textarea is monospace, Insert file inserts the upload JSON's + `html` field. Preview does not touch the server: the text goes into + `<iframe sandbox srcdoc>` (`.ed-preview-frame`) — an opaque origin with no + 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. - **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 @@ -342,7 +360,7 @@ 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`; control: `00001_init`, `00002_upload_limit`. Both chains were re-baselined at 00001 after + `00003_files`, `00004_tags`, `00005_search`, `00006_format`; 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 |
