Add Gruvbox share theme + blog system for Trilium (anonymized)
Blog generator, publish manager, share scripts, theme CSS, highlight.js bundles, font, favicon, templates and generated-reference outputs, plus an English installation guide.
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
# Trilium Share – Gruvbox Blog System
|
||||
|
||||
A complete **public-share blog system + Gruvbox theme** for [TriliumNext](https://github.com/TriliumNext/Trilium).
|
||||
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/`).
|
||||
|
||||

|
||||
|
||||
## 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**: `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/`:
|
||||
|
||||
```nginx
|
||||
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) and `blog_data`
|
||||
(search index used by the header search + 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
|
||||
|
||||
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.
|
||||
Reference in New Issue
Block a user