diff options
| author | grm <grm@eyesin.space> | 2026-09-17 21:29:44 +0300 |
|---|---|---|
| committer | grm <grm@eyesin.space> | 2026-09-17 21:29:44 +0300 |
| commit | e96bbec551c0388e972894b349413fa6ef607352 (patch) | |
| tree | c63b50d515fc0a0f754f970b8d316dd37f73ac80 /AGENTS.md | |
| parent | 69912c4ac04ada3286c09400ce523d5d3ab4cfb4 (diff) | |
| download | blogspace-e96bbec551c0388e972894b349413fa6ef607352.tar.gz blogspace-e96bbec551c0388e972894b349413fa6ef607352.tar.bz2 blogspace-e96bbec551c0388e972894b349413fa6ef607352.zip | |
Add custom fonts: upload WOFF2/WOFF/TTF/OTF files and pick them on the Design tab
Fonts become a file kind of their own (sniffed from the bytes like images,
served inline from /media), and the theme gets custom_font and
custom_heading_font: a chosen file is declared as @font-face and put first
in the stack, the built-in Font / Heading font choice behind it as the
fallback, so there is no "custom" enum value and no invalid state. The
pickers are plain selects with an upload input, no script. Fonts uploaded
before this are reclassified by the migration.
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 | 26 |
1 files changed, 20 insertions, 6 deletions
@@ -185,13 +185,23 @@ internal/web/ server.go (host router, middleware, render helpers) header text colour; hidden on phones so a long title can wrap. `NavStyle` `plain` is regular weight + underline for the menu links (blog.css makes them bold). + **Custom fonts** (`CustomFont`, `CustomHeadingFont`: font file ids or ""): + a chosen file is declared as `@font-face` (`"BlogFont"` / `"BlogHeadingFont"`, + `src:url(/media/<id>)`, same-origin so no CORS) and put first in the stack, + the built-in `Font`/`HeadingFont` choice behind it as the fallback — so + there is no "custom" enum value and no invalid state. `HeadingFontFamily` + builds on the text's stack when `HeadingFont` is `same`, and the `h1…h6` + rule is emitted whenever `HeadingFontSet` (a heading choice or a heading + file). Picked with the `fontpick` partial: a plain `<select>` of the newest + `recentImages` library fonts (`kind = font`) plus the chosen older one, and + an upload input — no library panel, no script. 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: 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. + one's filename, fonts included), so the page never grows with the library; `none` clears, a + uuid selects, no value keeps (`pickFile`); an upload in `<name>_file` wins. 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 @@ -245,15 +255,18 @@ internal/web/ server.go (host router, middleware, render helpers) - **Files** (`files` table, `store/files.go`, `handlers_files.go`, `filetype.go`, `/b/{sub}/files…`): the upload library, any type. A row is `id, filename, content_type, kind, size, data`; `kind` (image, document, - audio, video, archive, other) is decided at upload by `fileType`, which + audio, video, font, archive, other) is decided at upload by `fileType`, which trusts `http.DetectContentType` first and lets the extension refine only a generic sniff (text/plain, octet-stream) to a type on the allowlists in `filetype.go`; anything unknown is stored as `application/octet-stream`. + Fonts (`fontTypes`: WOFF2, WOFF, TTF, OTF) are known by their bytes alone, + like images; TTC and EOT stay `other`. `/media/{uuid}` is served on every host with immutable cache headers, from the host's blog database only (the root domain serves the root blog's files); dashboard previews for another blog on the root host use `GET /b/{sub}/media/{id}` (`withBlog`). **The root domain carries the - session cookie, so `servedAs` renders inline only images, PDF, plain text, + session cookie, so `servedAs` renders inline only images, fonts (a browser + parses one only inside `@font-face`), PDF, plain text, audio and video; everything else (HTML, SVG, XML, JS, archives, binaries) goes out as `application/octet-stream` + `Content-Disposition: attachment`.** `?download` forces attachment. SVG is therefore never an image (download @@ -293,7 +306,8 @@ internal/web/ server.go (host router, middleware, render helpers) `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 + for HTML) in the `*_html` column (`readUpload`'s last argument is the kind + the field must be — `"image"`, `"font"` — or `""` for anything) — 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 @@ -444,7 +458,7 @@ superadmin password to `admin`. Production refuses both. `-- +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`, `00007_post_image`, - `00008_page_outro`, `00009_gizmo_style`; control: `00001_init`, `00002_upload_limit`. Both chains were re-baselined at 00001 after + `00008_page_outro`, `00009_gizmo_style`, `00010_font_kind`; 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 |
