diff options
Diffstat (limited to 'AGENTS.md')
| -rw-r--r-- | AGENTS.md | 76 |
1 files changed, 56 insertions, 20 deletions
@@ -29,6 +29,11 @@ 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 + 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 + base-domain blog is only editable by the superadmin. - App runs plain HTTP behind a reverse proxy; auth is a JWT cookie. - Stdlib `net/http` ServeMux with method+pattern routes. No router library. @@ -38,14 +43,16 @@ table and deployment notes. cmd/blogspace/ main.go (serve|seed|migrate, superadmin + root blog bootstrap), seed.go internal/config/ env → Config; RootSubdomain = "www" internal/db/ pgxpool + goose; migrations/*.sql embedded -internal/store/ models + hand-written SQL (users, blogs, pages, posts, images, sections) +internal/store/ models + hand-written SQL (users, blogs, pages, posts, images, sections, modules, menu) internal/auth/ bcrypt, JWT issue/parse, cookie, HMAC CSRF internal/markdown/ Render(md) → sanitized HTML 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), theme.go + routes.go (all routes), handlers_*.go (sections = announcements, layout = modules + menu) + theme.go (colours/fonts/layout switches), layout.go (module kinds, archive grouping) templates/ (embedded; layouts/, partials/, auth/, dashboard/, admin/, blog/) - static/ (dashboard.css "paper & ink" look — plain CSS, no variables/flex; blog.css) + static/ (dashboard.css "paper & ink" look — plain CSS, no variables/flex; + blog.css — floats except the three-column `.cols`, which is flexbox) ``` ## Key mechanics @@ -79,27 +86,53 @@ internal/web/ server.go (host router, middleware, render helpers) Handlers call `s.render(w, r, "dir/file.html", map[string]any{...})`; page data is under `.Data.<key>`, common fields (`.User`, `.Blog`, `.CSRF`, `.Flash`, `.Error`, `.BlogURL`, `.RootURL`) are top level. - Flash messages travel as `?ok=` query param via `redirectOK`. Add new + 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`. `ParseTheme` merges over `DefaultTheme()` and `normalize()` clamps every - value to an allowlist (hex colours, enum strings, uuid image ids) — 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 + 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`, `FooterShowRSS`, `Favicon`) are read by the blog templates. + `FormatDate`, `Favicon`, `Logo`) are read by the blog templates. Optional colours (`LinkHover`, `NavHover`) are `""` = inherit and come - from a colour input paired with a `<name>_custom` checkbox. Image fields + 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: `none` clears, a uuid selects, no value keeps (`pickImage`); an upload in `<name>_file` wins. - Options that only apply to one menu layout (`NavAlign` for horizontal - menus, `NavSidebarWidth` for the sidebar) are shown/hidden in - `design.html` with CSS only (`#np-*:checked ~ .only-*` in dashboard.css, - same trick for the `_custom` hover checkboxes) — no JS, and browsers - without `:checked` just show everything. `Presets()`/`WithPreset` are the colour schemes (`POST /b/{sub}/design/preset`, - colours only); `POST /b/{sub}/design/reset` stores `DefaultTheme()`. + 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. +- **Layout** (`modules` table, `store/modules.go`, `web/layout.go`, + `handlers_layout.go`, `/b/{sub}/layout…`): 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, archive, recent, html, rss, + text, sitemap; `moduleKinds` says which kinds an area accepts). Modules + have an optional `title` (heading), `body` (raw HTML or footer text) and + `count` (recent posts). `blogView` builds a `Layout` (`buildLayout`, + areas that are switched off are dropped) and only fetches the archive + index / recent posts when a visible module needs them. A side column is + laid out only when it has modules, or always with `theme.keep_columns` + (`HasLeft/HasRight` → `body.has-*` + classes, which also lift the `.wrap` max-width so the page goes + full-width; column widths in the theme CSS under `@media (min-width: 701px)`); + below 700px `.cols` becomes block flow so the order is main → left → + right → footer. Everything is rendered by the `module` template in + `layouts/blog.html`. New blogs get `insertDefaultModules` (title + menu + in the header, RSS in the footer) — keep it in step with the layout + migration's defaults. +- **Menu** (`menu_items` table, `store/menu.go`): the single ordered list + the Menu module shows — page entries (`page_id`, unique per page, cascade + 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. - **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 @@ -116,9 +149,11 @@ internal/web/ server.go (host router, middleware, render helpers) `s.readUpload(r, "inline_image")` + `appendImageMD` (the no-JS path). - **Announcements** (`sections` table, `store/sections.go`, `handlers_sections.go`, `/b/{sub}/announcements…`): per-blog notices with - `placement` (above|below|sidebar), `style` (plain|note|warning), `enabled` - and `sort_order`. `blogView` loads the enabled ones and `splitSections` - routes `sidebar` to the left-sidebar layout only (otherwise above). + `placement` (`<column>-<position>`: left|main|right × top|bottom, split by + `Section.Column()/Position()`), `style` (plain|note|warning), `enabled` + and `sort_order`. `blogView` loads the enabled ones and `placeNotices` + groups them by placement, moving ones for a side column that is not laid + out into the main column. Rendered by the `notices` template in `layouts/blog.html`; CSS classes `notice notice-<style>` use translucent colours so they fit any theme. - **Dashboard nav**: `dashnav.html` marks the current tab with `hasPrefix @@ -160,4 +195,5 @@ superadmin password to `admin`. Production refuses both. ## Things deliberately not built (ask before adding) Drafts/preview, custom CSS, comments, multiple blogs per user, email, -user self-registration, custom domains per blog, image resizing, search. +user self-registration, custom domains per blog, image resizing, search, +sanitising the custom HTML module (owner's decision, see above). |
