Files

20 KiB
Raw Permalink Blame History

Trilium Share Coffee Blog System

License Live demo TriliumNext Theme Built with Model

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 three 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
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-harbor-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 (active) theme/blog_share_theme_tokyonight.css storm #24283b / fg #c0caf5, blue #7aa2f7 · day #e1e2e7 / fg #3760bf tokyonight.nvim (Apache-2.0)

Coffee palette Harbor palette Gruvbox palette Solarized palette Tokyo Night palette

Features

  • Coffee dark / Coffee Latte light theme for the share, with a theme toggle in the header (Gruvbox and Harbor 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 (dateText from 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: state labels (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.txt and index,follow meta. 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 / .stl files (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 showing name · size · triangle count. Large meshes are split into Uint16 chunks 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-pane widget see app/.
  • 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_manager moves notes between an Unpublished staging 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_theme_coffee.css Coffee / Coffee Latte share theme (active by default) 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
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)
    ├── Richard's 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_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
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
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)
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)
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 (Richard's 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 yes
~shareHtml hide home title no (index only)
~runOnNoteContentChange, ~runOnAttributeChange, ~runOnNoteCreation, ~runOnNoteTitleChange, ~runOnAttributeCreation, ~runOnBranchDeletion, ~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, Impressum or Search) is treated as a category. blogGarden marks 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__ Richard's 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 ID
  • BLOG_SYSTEM_NOTE_ID → the .blog note ID
  • UNPUBLISHED_ROOT_ID → the Unpublished note ID
  • BLOG_GENERATOR_ID → the generator note ID
  • SHARE_BASE → your public base URL, e.g. https://YOUR-INSTANCE.example.com
  • SITE_TITLE, SITE_DESCRIPTION → your blog name/tagline

5. Enable Public Share and generate

  1. On Richard's Blog right-click → Share → enable sharing.
  2. Run the generator once: execute the blog_generator script (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 add run=daily to it for a nightly run.
  3. Open the share (options → Public access → URL) the blog index is generated at the share root.

6. Publish an article

  1. Create a note from the templates/blog_article.html content (or clone the template note). It promotes the publish, date, category, summary, shareAlias and tags labels.
  2. Put it under a category inside Richard's Blog and set publish=true.
  3. To keep it private, put it under Unpublished instead blog_publish_manager clones it into the matching category (and back) when you flip publish.

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 .blog system note and are tagged with blogPagedFor.
  • Writes tag pages and the tags overview.
  • Regenerates feed.xml (RSS), sitemap.xml, state_map (garden states) and blog_data (search index with title, summary, tags, category and YYYY-MM-DD date used by the header search, the category-scoped prev/next and the date-sorted menu).
  • Sets shareDescription on every published note from its summary.

Customisation

  • Theme: pick one of theme/blog_share_theme_{coffee,harbor,gruvbox,solarized,tokyonight}.css and point the blog's shareCss relation at it see docs/switch-blog-theme.md. Each theme ships dark + light via html.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.js pattern 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.