Trilium Share Gruvbox 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 + Gruvbox 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/).

Gruvbox palette

Palette from the morhetz/gruvbox repository.

Features

  • Gruvbox light/dark theme for the share, with a theme toggle in the header.
  • 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 YYYY-MM-DD <shortened title> automatically.
  • 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.* (e.g. ngc281_thumb.jpg), that image is used instead ideal for serving small pre-made thumbnails.
  • 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.
  • Widget rendering for Mermaid, Excalidraw (canvas) and MindElixir (mind map) notes, with a collapsible, highlighted "Source" block and copy button.
  • 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.css the Gruvbox 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/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
templates/blog_article.html article template with promoted labels text
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.css theme/blog_share_theme.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
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)

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.css yes
~shareFavicon favicon yes
~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 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
.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.

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
__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 and category used by the header search and the category-scoped prev/next).
  • Sets shareDescription on every published note from its summary.

Customisation

  • Theme: edit theme/blog_share_theme.css (Gruvbox light/dark via html.theme-light/dark).
  • 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), assets/palette.png (MIT) and the embedded Trilium logo in scripts/bmc_button.js (AGPL-3.0). See THIRD_PARTY_LICENSES.md for details.

S
Description
No description provided
Readme GPL-3.0
13 MiB
Languages
JavaScript 68.1%
CSS 29.3%
EJS 2.5%
HTML 0.1%