125 lines
8.6 KiB
Markdown
125 lines
8.6 KiB
Markdown
# AGENTS.md – Trilium Share Gruvbox Blog System
|
||
|
||
Public repository for the **Gruvbox public-share theme + blog system** that runs on a
|
||
[TriliumNext](https://github.com/TriliumNext/Trilium) instance (`notes.familie-zink.org`,
|
||
live blog: `richard.familie-zink.org`). This file captures the requirements and design
|
||
decisions so future work stays consistent.
|
||
|
||
## What this is
|
||
|
||
A blog built on Trilium's Public Share: a backend **blog generator** builds index/category/tag
|
||
pages, an RSS feed, a sitemap, a search index and a garden-state map from notes that carry a
|
||
`publish=true` label. Frontend `shareHtml` scripts add search, prev/next, reading time,
|
||
widget rendering (Mermaid/Excalidraw/MindMap) and a themed footer. All styling lives in one
|
||
CSS file (Gruvbox, light + dark, theme toggle without reload).
|
||
|
||
## Repository layout
|
||
|
||
| Path | Note in Trilium | Purpose |
|
||
|---|---|---|
|
||
| `blog_generator.js` | `blog_generator` (backend script) | builds index, categories, tags, feed, sitemap, `blog_data`, `state_map` |
|
||
| `blog_publish_manager.js` | `blog_publish_manager` (backend script) | mirrors notes between `Unpublished` and the blog on `publish` label changes |
|
||
| `theme/blog_share_theme.css` | `blog_share_theme.css` (`shareCss`) | the entire Gruvbox theme |
|
||
| `theme/favicon.svg`, `theme/cookie.woff2` | `favicon`, `Cookie` | site icon + BMC font |
|
||
| `lib/highlight.js`, `lib/hljs_line_numbers.js` | `highlight.js`, `hljs line numbers plugin` | code highlighting |
|
||
| `lib/3d/*` | three.js, OrbitControls, STLLoader, occt-import-js (WASM) | local libraries for the 3D model viewer |
|
||
| `scripts/*.js` | one code note each (`.blog`), attached via `~shareHtml` | frontend snippets (verbatim content incl. `<script>`) |
|
||
| `templates/blog_article.html` | `Blog Article` | article template with promoted labels |
|
||
| `examples/*` | ignis8 icons/script | sample category logo |
|
||
| `generated/*` | `feed.xml`, `sitemap.xml`, `blog_data`, `state_map` | regenerated by the generator (reference only) |
|
||
| `assets/palette.png` | – | Gruvbox palette (from morhetz/gruvbox, MIT) |
|
||
|
||
## How the generator works (keep this intact)
|
||
|
||
- Collects notes with `publish=true` under the blog root (skips `blogGarden` subtrees).
|
||
- **Index**: page 1 = blog root, further pages as notes under `.blog` named by
|
||
`blogPagedFor` + `blogPagedNumber` labels, `shareAlias = <alias>-<n>` (`blog-2`, …).
|
||
- **Categories**: any top-level note under the blog root that is not a system note
|
||
(`Tags`, `.blog`, `Impressum`, `About Me`, `Search`) is a category; `blogGarden`
|
||
marks a growth-state garden. Category pages are paginated the same way.
|
||
- **Sorting**: index, categories, tags and `blog_data` are all sorted by publication
|
||
date descending (`date` label, fallback `dateCreated`). Never change this.
|
||
- **Pagination**: `POSTS_PER_PAGE = 10`. Prev/next on index/category pages link only
|
||
between that section's pages (server-side `.blog-pagination`), not globally.
|
||
- **Article prev/next** (`scripts/prev_next.js`) is scoped to the article's category
|
||
(`blog_data.articles[].category`); falls back to the global list when uncategorized.
|
||
- **Menu sorting** (`scripts/menu_date_sort.js`, `shareHtmlLocation=body:end`): reads
|
||
`blog_data.articles[].date` (generator exposes the `date` label, fallback `dateCreated`,
|
||
`YYYY-MM-DD`), re-sorts each category's menu entries by date descending and prefixes the
|
||
label with `YYYY-MM-DD <shortened title>` (icons preserved). Non-article entries are left
|
||
in place.
|
||
- **Thumbnails** (`thumbSrc()`): prefer a child note of the article titled `*_thumb.*`
|
||
(e.g. `ngc281_thumb.jpg`) → `api/notes/<id>/download`; otherwise use the first `<img>`
|
||
from the note content. Layout: image left, summary right (`.blog-post-inner`),
|
||
`max-height:256px`, lazy-loaded. **There is no server-side resizing** – see "Limitations".
|
||
- Regenerates `feed.xml`, `sitemap.xml`, `state_map` (garden states) and `blog_data`
|
||
(search index: id/title/url/summary/tags/category). Sets `shareDescription` from `summary`.
|
||
|
||
## Wiring / attributes (the ground truth)
|
||
|
||
- All inheritable relations live on the **blog root**: `~shareCss → blog_share_theme.css`,
|
||
`~shareFavicon → favicon`, `~shareHtml → <each script>`, `~runOnNoteContentChange/…
|
||
→ blog_generator`, `~runOnAttributeChange/Creation → blog_publish_manager`.
|
||
- Every script note carries `shareHtmlLocation` (`head:end`, `body:end`, `content:start`,
|
||
`body:start`) and `shareHiddenFromTree`.
|
||
- `feed.xml` → `shareAlias=feed` + `shareRaw`; `sitemap.xml` → `shareAlias=sitemap.xml` + `shareRaw`.
|
||
- Generator + publish manager are `application/javascript;env=backend`; generator has `run=daily`.
|
||
- Digital Garden category: `blogGarden`, `label:state` definition
|
||
(`promoted,alias=Growth Condition,single,select,options=Seed;Seedling;Sprout;Sprouting;Bud;Evergreen;Draft;Review`, inheritable), `~shareHtml → garden h1 green`.
|
||
- On import the note IDs differ → replace the `__X_ID__` tokens in scripts/generator (see README).
|
||
|
||
## Theme / design rules (keep consistent)
|
||
|
||
- **Gruvbox palette** only: bg `#282828`/`#1d2021`, fg `#ebdbb2`, red `#fb4934`,
|
||
green `#b8bb26`, yellow `#fabd2f`, blue `#83a598`, purple `#d3869b`, aqua `#8ec07c`,
|
||
orange `#fe8019`; light-mode fades from Gruvbox Light.
|
||
- **Radius system** (user decision, 2026-08-28): `8px` for code blocks/code notes,
|
||
inline code, embedded images, diagrams (mermaid/mindmap/excalidraw) and thumbnails;
|
||
`10px` for blog cards; `6px` for chips/tags/buttons/inputs; `4px` for tiny menu icons.
|
||
Embedded images and inline code use the **same 8px** radius.
|
||
- Inline code: `#content code { border-radius:8px; padding:.1em .35em }` (dark bg in both themes).
|
||
- Embedded images: `#content img:not(.blog-thumb) { border-radius:8px }`.
|
||
- **Figure captions**: `#content figcaption` is a Gruvbox chip (secondary bg, highlight
|
||
border, 8px radius, muted italic text, centered).
|
||
- **3D models** (`scripts/3d_model_viewer.js`): `.step`/`.stp`/`.stl` file notes (title ends
|
||
in the extension) or inline attachment links (link text ends in the extension) get an
|
||
interactive three.js viewer (drag/zoom), 8px rounded box + caption. STEP is parsed by
|
||
occt-import-js (OpenCascade WASM). All five libraries are hosted as local notes and
|
||
referenced via `__THREE_ID__`/`__THREE_ORBIT_ID__`/`__THREE_STL_ID__`/`__OCCT_JS_ID__`/
|
||
`__OCCT_WASM_ID__`. User guide: `docs/embed-3d-model.md`.
|
||
- Images in the share are served full-size only (no resize endpoint) → keep the
|
||
`_thumb` child-note convention for small thumbnails.
|
||
- Built-in share footer navigation and book children list are hidden
|
||
(`#content-footer .navigation`, `#index`).
|
||
- **SEO**: a custom share template (`templates/share/page.ejs`, note `share template`,
|
||
`application/x-ejs`, wired via `~shareTemplate` inheritable on the blog root, with
|
||
`tree_item`/`prev_next`/`toc_item` partials as children) adds per-page canonical,
|
||
`og:url`/`og:image`/`og:type`, Twitter cards, JSON-LD (WebSite/BlogPosting/CollectionPage)
|
||
and `index,follow`. The generator's `ensureSeoMeta()` sets `shareDescription` +
|
||
`shareOpenGraphURL` on every published note and category. `robots.txt`
|
||
(`shareAlias=robots.txt`, `shareRaw`) points at the sitemap; `~shareOpenGraphImage`
|
||
on the blog root sets the default OG image.
|
||
|
||
## Limitations / gotchas
|
||
|
||
- **Backend script module whitelist is hardcoded** (`dayjs`, `marked`, `turndown`,
|
||
`cheerio`, `axios`, `xml2js`, `escape-html`, `sanitize-html`, `lodash`); `sharp`,
|
||
`fs`, `child_process`, `http` etc. are blocked. Therefore **image resizing inside a
|
||
backend script is impossible** without patching Trilium source. Do not attempt to add
|
||
sharp-based code to the generator.
|
||
- `occt-import-js` / OpenCASCADE (used by the 3D viewer) is **LGPL-2.1** (see
|
||
`THIRD_PARTY_LICENSES.md`); three.js is MIT. Both are hosted locally under `lib/3d/`.
|
||
- Triggering the generator via an attribute change on the **blog root itself** is ignored
|
||
by the self-trigger guard (`isGeneratedNoteId(BLOG_ROOT_ID) → true`). To force a full
|
||
regeneration, change an attribute/content on a **published article** instead.
|
||
- The share never resizes images; the `_thumb` child-note convention is the intended path
|
||
for bandwidth-friendly thumbnails.
|
||
- Comments/commits in the repo are English; communication with the owner is German.
|
||
|
||
## Releasing changes
|
||
|
||
Edit the corresponding Trilium note (live), verify on `richard.familie-zink.org`, then mirror
|
||
the anonymized file into this repo (replace `richard.familie-zink.org` → `https://YOUR-INSTANCE.example.com`,
|
||
`Richard's Blog` → `Your Blog`, and the note-ID tokens → `__…__`), update `README.md` if the
|
||
feature list changed, commit and push to `main`. Never commit real note IDs, the ETAPI token,
|
||
or personal data (name/domain are allowed in the README demo badge only). |