Trilium Share – Gruvbox Blog System
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/).
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.
- 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.
- 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_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_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,ImpressumorSearch) is treated as a category.blogGardenmarks 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 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
Richard's 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
Richard's 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 (article cards), category pages (sub-category chips + articles,
respecting a
<!-- blog:articles -->marker so your hand-written intro text is kept), tag pages and the tags overview. - Regenerates
feed.xml(RSS),sitemap.xml,state_map(garden states) andblog_data(search index used by the header search + prev/next). - Sets
shareDescriptionon every published note from itssummary.
Customisation
- Theme: edit
theme/blog_share_theme.css(Gruvbox light/dark viahtml.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.jspattern to give a category a custom menu icon and title logo.
Acknowledgements
This theme and blog system was created with the help of Opencode and Deepseek V4 Flash.
License
The vendored highlight.js is licensed under BSD-3-Clause (see its header). All other files in
this repository are provided as-is – see the original author's repository for details.
