- new snippet geo_map_render.js: [map] inline geomaps (tree order, tint, theme-following), [gpx] stats card + elevation profile with hover cursor (km from start, d-height, vertical guide), [yt] privacy click-to-load facade, [video]/[audio] inline players fed from the new geoData indexes - 3d_model_viewer.js: [cad] keyword + title/ID resolution via geoData.cad, sizes/captions, real occt-wasm ESM pair (fixes 'STEP worker error'), code-span-safe keyword scan (docs can show examples) - blog_generator.js: buildGeoData() v6 – maps (geolocation + geoShape point/line/polygon + child GPX tracks), gpx/cad/media indexes, branch-change rebuild for route reordering - generated/geo_data.js placeholder; docs/embed-keywords.md (new) + embed-3d-model Option D; README layout/wiring/notes tables updated - license: runtime components (MapLibre ISC, VersaTiles MIT/ODbL, YouTube) - hygiene: removed leaked note IDs and instance domain from theme docs
22 KiB
Trilium Share – Coffee Blog System
Note: This theme and blog system was created with the help of Opencode and Deepseek V4 Flash.
A complete public-share blog system + Coffee theme for TriliumNext. It turns a normal Trilium note tree into a themed, searchable blog with categories, an RSS feed, a sitemap, a digital garden (growth states) and client-side rendering of Mermaid, Excalidraw and mind-map notes.
Everything is driven by Trilium's built-in Public Share feature plus a few frontend/backend scripts –
no external dependencies (except highlight.js, vendored in lib/).
Themes
The share ships with six themes, each with a dark and a light variant (toggled via the header
switch / html.theme-light + html.theme-dark). They live in theme/ and are set on the blog by
pointing the shareCss relation at the matching note – see
docs/switch-blog-theme.md for the step-by-step guide.
| Theme | File | Palette (dark / light) | Source |
|---|---|---|---|
| Ash (active) | theme/blog_share_theme_ash.css |
panel #1c1b1f / bone #e6dfd1, ember #e0914f steel #7c9aa1 · paper #e9e3d5 / ink #2a2822 |
Ash palette, Gruvbox-derived (trilium-themes) |
| Coffee | theme/blog_share_theme_coffee.css |
espresso #1d150f / latte-cream #f0e2d0, caramel #e0a15c · cream #E8E3C3 / ink #2D1A1D, berry #8e2d53 |
megabyte0x/omarchy-coffee-theme (MIT) |
| Harbor | theme/blog_share_theme_harbor.css |
near-black #1b1b1b / cream #efebdc, coral #e75a50 · paper #dfe4c4 / ink #1c2d28, nord-blue #5e81ac |
HANCORE-linux/omarchy-harbordark-theme (MIT) |
| Gruvbox | theme/blog_share_theme_gruvbox.css |
bg #282828 / fg #ebdbb2, accents red/green/yellow/blue |
morhetz/gruvbox (MIT) |
| Solarized | theme/blog_share_theme_solarized.css |
bg #002b36 / fg #839496, blue #268bd2 cyan #2aa198 · bg #fdf6e3 / fg #657b83 |
Solarized by Ethan Schoonover (MIT) |
| Tokyo Night | theme/blog_share_theme_tokyonight.css |
storm #24283b / fg #c0caf5, blue #7aa2f7 · day #e1e2e7 / fg #3760bf |
tokyonight.nvim (Apache-2.0) |
Features
- Coffee dark / Coffee Latte light theme for the share, with a theme toggle in the header (Ash, Harbor, Gruvbox, Solarized and Tokyo Night are available as well – see the Themes section).
- Blog index, category pages, sub-categories, tags and tag overview.
- Menu sorting: articles in the left menu are sorted by publication date (newest first)
and labelled
Fri 2026-08-28 <shortened title>automatically. All date displays (post cards, garden table, menu) include the weekday (dateTextfrom the generator). - Article thumbnails: each post card shows the first image of the article on the left
(bounded by the teaser height), summary on the right, lazy-loaded. If an article has a child
note named
thumb.<ext>or*_thumb.<ext>(e.g.thumb.png,ngc281_thumb.jpg), that image is used instead – ideal for serving small pre-made thumbnails. Thumb child notes are auto-hidden from the blog menu (shareHiddenFromTree). - Pagination: 10 posts per page on the index and category pages, with prev/next navigation between the pages; article prev/next links stay within the article's category.
- Article metadata: date, category, summary, tags, prev/next navigation, reading time.
- Digital Garden:
statelabels (Seed → Evergreen, Draft, Review) rendered as badges and colour-coded growth tables. - Search (client-side over a generated index), RSS feed and sitemap.xml.
- SEO: per-page canonical + Open Graph (
og:url,og:image, article/website type), Twitter cards, JSON-LD structured data (WebSite / BlogPosting / CollectionPage),robots.txtandindex,followmeta. Provided by a custom share template (templates/share/page.ejs). - Widget rendering for Mermaid, Excalidraw (canvas) and MindElixir (mind map) notes, with a collapsible, highlighted "Source" block and copy button.
- 3D models:
.step/.stp/.stlfiles (as file notes, as inline attachments, or linked from text notes) render in an interactive three.js viewer (STEP via occt-wasm, OpenCASCADE V8, parsed in a non-blocking module worker). The viewer is full-bleed (~100vw) with a viewport-based height, drag to rotate / scroll to zoom, rounded 8px box, a loading progress bar and a caption showingname · size · triangle count. Large meshes are split intoUint16chunks so mobile GPUs render them too (≈480k triangles for the demo battery module). See docs/embed-3d-model.md. - 3D viewer inside Trilium (app widget): the same viewer runs in the Trilium app itself
(independent of the blog) via a
note-detail-panewidget – seeapp/. - Inline embed keywords: plain-text keywords render interactive widgets in any shared note —
[map](MapLibre + VersaTiles vector maps with route lines, GPX/geoShape tracks and popups),[gpx](track stats + elevation profile with hover measurement cursor),[cad](3D viewer),[yt](privacy-friendly YouTube click-to-load facade),[video]/[audio](stream media file notes). Everything follows the blog theme. See docs/embed-keywords.md. - Code highlighting (highlight.js + line numbers) and copy buttons on all code boxes.
- Optional "Buy me a coffee" button with locally-hosted Cookie font.
blog_generator(backend) builds index/feed/sitemap/search-data;blog_publish_managermoves notes between anUnpublishedstaging area and the live blog.
Repository layout
| Path | Content | Trilium note type / mime |
|---|---|---|
blog_generator.js |
backend script that generates index, feed, sitemap, search data, category & tag pages | code, application/javascript;env=backend |
blog_publish_manager.js |
backend script that mirrors notes between Unpublished and the blog |
code, application/javascript;env=backend |
theme/blog_share_base.css |
Base stylesheet (layout/components, palette-independent, var()-driven) | code, text/css |
theme/blog_share_theme_ash.css |
Ash / Ash light share theme (currently active) – Gruvbox-derived palette | code, text/css |
theme/blog_share_theme_coffee.css |
Coffee / Coffee Latte share theme (palette overrides) | code, text/css |
theme/blog_share_theme_harbor.css |
Harbor / Harbordark share theme | code, text/css |
theme/blog_share_theme_gruvbox.css |
Gruvbox light/dark share theme | code, text/css |
theme/blog_share_theme_solarized.css |
Solarized dark/light share theme | code, text/css |
theme/blog_share_theme_tokyonight.css |
Tokyo Night (storm/day) share theme | code, text/css |
theme/favicon.svg |
default site favicon | code (SVG) |
theme/cookie.woff2 |
"Cookie" font for the Buy me a coffee button | file, font/woff2 |
lib/highlight.js |
vendored highlight.js | code, application/javascript |
lib/3d/* |
three.js + OrbitControls + STLLoader + occt-wasm (OCCT V8 bundle + WASM) for the 3D viewer | code / file |
lib/hljs_line_numbers.js |
highlight.js line-numbers plugin | code |
scripts/*.js |
frontend share snippets (content is pasted verbatim into a code note, including the <script>/<style> wrapper) |
code |
app/3d-model-viewer-app.jsx |
JSX widget that renders the 3D viewer for model file notes inside the Trilium app (label #widget=3dModelViewer) |
code, text/jsx |
app/3d-worker.js |
Web Worker (STEP parsing) loaded by the app widget from a code note (CSP-safe) | code, text/javascript |
templates/blog_article.html |
article template with promoted labels | text |
templates/share/*.ejs |
custom share template + partials (canonical, OG, JSON-LD, robots) | code, application/x-ejs |
assets/og-banner.png |
default Open Graph image (1200×630, replace with your own) | file |
docs/embed-3d-model.md |
how to embed STEP/STL in a note | – |
docs/embed-keywords.md |
reference for the inline embed keywords (map/gpx/cad/yt/video/audio) | – |
examples/* |
sample category logo + menu icon script (ignis8) | code / SVG |
generated/* |
reference output, regenerated automatically by blog_generator.js |
code / xml |
The file content of a
scripts/*note already contains the<script>/<style>tags. When you create the code note, paste the whole file into the note content.
Prerequisites
- TriliumNext server with Public Share enabled (Options → Public access).
- A public hostname (optional but recommended, e.g. for
sitemap.xml/ OG tags). - Basic familiarity with Trilium attributes (labels & relations).
Installation
1. Create the note structure
root
└── Blog (book)
├── Your Blog (book) ← blog root, the shared note
│ ├── .blog (book) ← system notes (scripts, css, data)
│ ├── Astrophotography (book) ← example category
│ ├── Electronic (book)
│ ├── Leathercraft (book)
│ ├── Digital Garden (book) ← growth-state garden category
│ ├── Tags (text) ← auto-filled tag overview
│ ├── About Me (text)
│ └── Impressum (text)
├── Unpublished (book) ← staging area
├── blog_generator (code, backend)
└── blog_publish_manager (code, backend)
You can use any titles; the generator finds notes by the constants at the top of
blog_generator.js (BLOG_ROOT_NOTE_ID, BLOG_SYSTEM_NOTE_ID, TAGS_SECTION_TITLE).
2. Create the code notes
Under .blog create one code note per file, using the file name as the note title
(optional, but matches the generator):
| Note title | File | mime |
|---|---|---|
blog_share_theme_ash.css |
theme/blog_share_theme_ash.css |
text/css |
blog_share_theme_coffee.css |
theme/blog_share_theme_coffee.css |
text/css |
blog_share_theme_harbor.css |
theme/blog_share_theme_harbor.css |
text/css |
blog_share_theme_gruvbox.css |
theme/blog_share_theme_gruvbox.css |
text/css |
blog_share_theme_solarized.css |
theme/blog_share_theme_solarized.css |
text/css |
blog_share_theme_tokyonight.css |
theme/blog_share_theme_tokyonight.css |
text/css |
favicon |
theme/favicon.svg |
– (SVG) |
Cookie |
theme/cookie.woff2 |
font/woff2 (file note) |
highlight.js |
lib/highlight.js |
application/javascript |
hljs line numbers plugin |
lib/hljs_line_numbers.js |
text/javascript |
hide home title |
scripts/hide_home_title.js |
text/html |
garden h1 green |
scripts/garden_h1_green.js |
text/html |
table column hover |
scripts/table_column_hover.js |
text/html |
blog card click |
scripts/blog_card_click.js |
text/html |
garden row click |
scripts/garden_row_click.js |
text/html |
code note highlight |
scripts/code_note_highlight.js |
text/html |
code copy & lines |
scripts/code_copy_lines.js |
text/html |
title state badge |
scripts/title_state_badge.js |
text/html |
bmc button |
scripts/bmc_button.js |
text/html |
logo share alias |
scripts/logo_share_alias.js |
text/html |
prev next |
scripts/prev_next.js |
text/html |
reading time |
scripts/reading_time.js |
text/html |
search header |
scripts/search_header.js |
text/html |
related posts |
scripts/related_posts.js |
text/html |
note fallback |
scripts/note_fallback.js |
text/html |
menu date sort |
scripts/menu_date_sort.js |
text/html |
3d model viewer |
scripts/3d_model_viewer.js |
text/html |
geo map render |
scripts/geo_map_render.js |
text/html |
three.js |
lib/3d/three.min.js |
application/javascript |
three orbit controls |
lib/3d/OrbitControls.js |
application/javascript |
three stl loader |
lib/3d/STLLoader.js |
application/javascript |
occt-import-js |
lib/3d/occt-import-js.js |
application/javascript |
occt-import-js.wasm |
lib/3d/occt-import-js.wasm |
application/wasm (file note) |
occt-wasm bundle |
lib/3d/occt-wasm-bundle.js |
application/javascript (ESM OcctKernel, used by the [cad] viewer worker) |
occt-wasm.wasm |
lib/3d/occt-wasm.wasm |
application/wasm (file note) |
feed.xml |
generated/feed.xml |
code (regenerated) |
sitemap.xml |
generated/sitemap.xml |
code (regenerated) |
state_map |
generated/state_map.js |
text/html (regenerated) |
blog_data |
generated/blog_data.js |
text/html (regenerated) |
geo_data |
generated/geo_data.js |
text/html (regenerated; auto-created and auto-attached as shareHtml by buildGeoData()) |
robots.txt |
generated/robots.txt |
code (static) |
share template |
templates/share/page.ejs |
application/x-ejs |
tree_item, prev_next, toc_item (children of share template) |
templates/share/tree_item.ejs etc. |
application/x-ejs |
og-banner |
assets/og-banner.png |
image note |
Also create, under the Blog book: blog_generator (from blog_generator.js,
mime application/javascript;env=backend) and blog_publish_manager (from
blog_publish_manager.js, mime application/javascript;env=backend).
3. Wire the attributes
On the blog root note (Your Blog), add:
| Attribute | Value | Inheritable |
|---|---|---|
shareRoot |
true |
– |
shareAlias |
blog |
– |
shareIndex |
(empty) | – |
shareDescription |
e.g. Project documentation & technical blog |
– |
shareOpenGraphURL |
https://YOUR-INSTANCE.example.com/ |
– |
shareOpenGraphColor |
#83a598 |
– |
iconClass |
bx bx-edit-alt |
– |
~shareCss |
→ blog_share_theme_coffee.css (the active theme) |
yes |
~shareFavicon |
→ favicon |
yes |
~shareTemplate |
→ share template |
yes |
~shareOpenGraphImage |
→ og-banner |
– |
~shareHtml |
→ table column hover, blog card click, garden row click, code note highlight, code copy & lines, state_map, title state badge, bmc button, logo share alias, blog_data, prev next, reading time, search header, ignis8 menu icon (optional), note fallback, menu date sort, 3d model viewer, geo map render |
yes |
~shareHtml |
→ hide home title |
no (index only) |
~runOnNoteContentChange, ~runOnAttributeChange, ~runOnNoteCreation, ~runOnNoteTitleChange, ~runOnAttributeCreation, ~runOnBranchDeletion, ~runOnBranchChange, ~runOnChildNoteCreation |
→ blog_generator |
yes |
~runOnAttributeChange, ~runOnAttributeCreation |
→ blog_publish_manager |
yes |
On the script notes (under .blog), add:
| Note | Attribute |
|---|---|
hide home title |
shareHtmlLocation=head:end, shareHiddenFromTree |
garden h1 green |
shareHtmlLocation=head:end, shareHiddenFromTree |
state_map |
shareHtmlLocation=body:start, shareHiddenFromTree |
title state badge |
shareHtmlLocation=content:start, shareHiddenFromTree |
| every other script | shareHtmlLocation=body:end, shareHiddenFromTree |
highlight.js |
shareHiddenFromTree |
feed.xml |
shareAlias=feed, shareRaw, shareHiddenFromTree |
sitemap.xml |
shareAlias=sitemap.xml, shareRaw, shareHiddenFromTree |
robots.txt |
shareAlias=robots.txt, shareRaw, shareHiddenFromTree |
.blog (book) |
shareHiddenFromTree, viewType=list |
On the Digital Garden category note (and any other garden category), add:
| Attribute | Value | Inheritable |
|---|---|---|
blogGarden |
(empty) | – |
shareAlias |
digital-garden |
– |
iconClass |
bx bx-leaf |
– |
label:state |
promoted,alias=Growth Condition,single,select,options=Seed;Seedling;Sprout;Sprouting;Bud;Evergreen;Draft;Review |
yes |
~shareHtml |
→ garden h1 green |
– |
Categories: any top-level note under the blog root (that is not
Tags,.blog,About Me,ImpressumorSearch) is treated as a category.blogGardenmarks it as a growth-state garden.Project landing pages: to turn a category into a pure project description page instead of an article collector, add
blogNoArticles(empty label) to the category note. The generator then keeps only the user text above the<!-- blog:articles -->marker and does not append the article list; the articles stay reachable via the tree and the "Subpages" child links.
4. Replace the placeholder note IDs
Trilium assigns random note IDs on creation, so the scripts that reference notes by ID contain
placeholder tokens. Replace every __TOKEN__ with the real note ID of the corresponding note:
| Token | Replace with note |
|---|---|
__BLOG_ROOT_ID__ |
Your Blog |
__BLOG_SYSTEM_ID__ |
.blog |
__UNPUBLISHED_ROOT_ID__ |
Unpublished |
__BLOG_GENERATOR_ID__ |
blog_generator |
__HIDE_HOME_TITLE_ID__ |
hide home title |
__HLJS_ID__ |
highlight.js |
__HLJS_LN_ID__ |
hljs line numbers plugin |
__COOKIE_FONT_ID__ |
Cookie |
__THREE_ID__ |
three.js |
__THREE_ORBIT_ID__ |
three orbit controls |
__THREE_STL_ID__ |
three stl loader |
__OCCT_JS_ID__ |
occt-import-js |
__OCCT_WASM_ID__ |
occt-import-js.wasm |
__TITLE_ICON_ID__ |
(only needed for the ignis8 example) |
__CATEGORY_NOTE_ID__ |
(only needed for the ignis8 example) |
Then edit the constants at the top of blog_generator.js and blog_publish_manager.js:
BLOG_ROOT_NOTE_ID/BLOG_ROOT_ID→ the blog root note IDBLOG_SYSTEM_NOTE_ID→ the.blognote IDUNPUBLISHED_ROOT_ID→ theUnpublishednote IDBLOG_GENERATOR_ID→ the generator note IDSHARE_BASE→ your public base URL, e.g.https://YOUR-INSTANCE.example.comSITE_TITLE,SITE_DESCRIPTION→ your blog name/tagline
5. Enable Public Share and generate
- On
Your Blogright-click → Share → enable sharing. - Run the generator once: execute the
blog_generatorscript (e.g. press play in the script editor, or trigger any attribute/content change). It is also wired to run automatically on every relevant change (runOn*), and you can addrun=dailyto it for a nightly run. - Open the share (options → Public access → URL) – the blog index is generated at the share root.
6. Publish an article
- Create a note from the
templates/blog_article.htmlcontent (or clone the template note). It promotes thepublish,date,category,summary,shareAliasandtagslabels. - Put it under a category inside
Your Blogand setpublish=true. - To keep it private, put it under
Unpublishedinstead –blog_publish_managerclones it into the matching category (and back) when you flippublish.
7. Optional: clean hostname via nginx
If you want a clean domain instead of /share, proxy it and rewrite / to /share/:
server {
server_name blog.example.com;
location / {
proxy_pass https://notes.example.com/share/;
proxy_set_header Host notes.example.com;
}
}
How the generator works
- Walks the blog root and collects notes with
publish=true. - Writes the index (first 10 posts) plus page notes (
blog-2,blog-3, …) with prev/next navigation between the pages. - Writes category pages (sub-category chips + articles, respecting a
<!-- blog:articles -->marker so your hand-written intro text is kept), paginated the same way; page notes live in the.blogsystem note and are tagged withblogPagedFor. - Writes tag pages and the tags overview.
- Regenerates
feed.xml(RSS),sitemap.xml,state_map(garden states) andblog_data(search index with title, summary, tags, category andYYYY-MM-DDdate – used by the header search, the category-scoped prev/next and the date-sorted menu). - Sets
shareDescriptionon every published note from itssummary.
Customisation
- Theme: pick one of
theme/blog_share_theme_{ash,coffee,harbor,gruvbox,solarized,tokyonight}.cssand point the blog'sshareCssrelation at it – seedocs/switch-blog-theme.md. Each theme ships dark + light viahtml.theme-light/dark. The 3D viewer and Mermaid rendering read the theme's CSS variables, so they adapt automatically to whichever theme is active. - Buy me a coffee: edit
scripts/bmc_button.js(link, text, copyright) – the font is referenced via__COOKIE_FONT_ID__. - Category logos: copy the
examples/icon +ignis8_menu_icon.jspattern to give a category a custom menu icon and title logo.
License
This project is licensed under the GNU General Public License v3.0 – see LICENSE.
The repository also contains third-party assets that keep their own licenses:
lib/highlight.js (BSD-3-Clause), lib/hljs_line_numbers.js (MIT),
theme/cookie.woff2 (SIL OFL 1.1), the palette images assets/palette-*.png (MIT) and the embedded
Trilium logo in scripts/bmc_button.js (AGPL-3.0). See
THIRD_PARTY_LICENSES.md for details.





