feat(embeds): geo/gpx/3D/YouTube/media keyword system + docs, sanitized mirrors
- 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
This commit is contained in:
+17
-1
@@ -7,7 +7,7 @@ available space instead of the readable content width. **Inline viewers embedded
|
||||
articles** (Options B and C) are limited to the content width (`model-viewer-inline`). The caption below the model always shows
|
||||
`<file name> · <size> · <triangle count>` (e.g. `Wrench.stl · 711 KB · 14.6k triangles`).
|
||||
|
||||
There are three ways to embed a model, depending on how you store the file.
|
||||
There are four ways to embed a model, depending on how you store the file.
|
||||
|
||||
## Option A – Model as its own note (file note)
|
||||
|
||||
@@ -47,6 +47,22 @@ exists as its own (published) file note — the model stays in one place.
|
||||
> STEP tessellation is set to `linearDeflection: 0.4, angularDeflection: 0.2`
|
||||
> (≈480k triangles for the ignis battery module; tuned for mobile).
|
||||
|
||||
## Option D – `[cad]` keyword (editor-safe, no HTML or links)
|
||||
|
||||
Plain-text keyword, robust against editor re-saves (unlike raw HTML embeds, which CKEditor strips):
|
||||
|
||||
```
|
||||
[cad <id|title>] # e.g. [cad base] or [cad __MODEL_FILE_NOTE_ID__]
|
||||
[cad <id|title>|small] # small | medium | large (viewer height)
|
||||
[cad <id|title>|Own caption] # free text as caption
|
||||
```
|
||||
|
||||
The key resolves through the **`cad` index** in `window.geoData` (built by `blog_generator.js`,
|
||||
scans the blog tree for `.stl` / `.step` / `.stp` file notes; the extension may live in the
|
||||
note title **or** `#originalFileName`). The model file note does **not** need its own `publish`
|
||||
label — it only has to be inside the blog subtree. See `docs/embed-keywords.md` for the whole
|
||||
keyword family (`map`, `gpx`, `cad`, `yt`, `video`, `audio`).
|
||||
|
||||
## Requirements
|
||||
|
||||
- The `3d model viewer` script must be wired: `~shareHtml → 3d model viewer`
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# Inline embed keywords (map / gpx / cad / yt / video / audio)
|
||||
|
||||
The share renders small **text keywords** into interactive widgets, client-side, from two
|
||||
`shareHtml` snippets: `scripts/geo_map_render.js` (`map`, `gpx`, `yt`, `video`, `audio`) and
|
||||
`scripts/3d_model_viewer.js` (`cad`). Keywords are plain text, so they survive editor re-saves —
|
||||
no raw HTML needed. Put one keyword **on its own line** (recommended) or inline in a sentence.
|
||||
|
||||
A keyword is ignored when it sits inside `code`/`pre` (that is how this file shows examples).
|
||||
|
||||
## Syntax
|
||||
|
||||
```
|
||||
[map <id|title>] # geomap, optional: |small|medium|large
|
||||
[gpx <id|title>] # track stats card + elevation profile, optional: |Custom title
|
||||
[cad <id|title>] # 3D model viewer, optional: |small|medium|large or |Caption
|
||||
[yt <videoId|url>] # YouTube click-to-load facade, optional: |Caption
|
||||
[video <id|title>] # inline video player, optional: |Caption
|
||||
[audio <id|title>] # inline audio player, optional: |Caption
|
||||
```
|
||||
|
||||
`<id|title>` is either the **note ID** (12 chars) or the **note title / file name**
|
||||
(case-insensitive; file extension optional).
|
||||
|
||||
## Requirements per keyword
|
||||
|
||||
| Keyword | References | Notes |
|
||||
|---|---|---|
|
||||
| `map` | a geoMap book note (`#viewType=geoMap` + `#publish=true`) | points via child `#geolocation` / `#geo:position` / `#geoShape` or child `.gpx` files; tree order = route order; reorder children in Trilium → auto refresh (`~runOnBranchChange`) |
|
||||
| `gpx` | any `.gpx` **file note** in the blog tree | stats: distance, ascent/descent (0.5 m deadband), elevation range, duration, avg speed, pace (needs `<time>` in track points) + hover cursor on the profile (km from start · Δheight from start) |
|
||||
| `cad` | `.stl` / `.step` / `.stp` file note | extension may live in the note title **or** `#originalFileName`; STEP parsed by occt-wasm in a module worker (~22 MB on first load) |
|
||||
| `yt` | YouTube video ID or URL (`youtu.be`, `watch?v=`, `/shorts/`) | only the thumbnail loads initially; player comes from `youtube-nocookie.com` on click |
|
||||
| `video` | `.mp4` / `.webm` / `.mov` / `.m4v` file note | use **faststart** MP4 (moov first). Trilium ignores `Range` requests → seeking may re-buffer |
|
||||
| `audio` | `.mp3` / `.ogg` / `.oga` / `.wav` / `.flac` / `.m4a` / `.aac` / `.opus` file note | streamed from `api/notes/<id>/download` |
|
||||
|
||||
`gpx` (as card), `cad`, `video` and `audio` resolve through the **`geoData` indexes**
|
||||
(`maps`, `gpx`, `cad`, `media`) which `blog_generator.js` builds automatically — so the files
|
||||
must live somewhere **inside the blog tree** (they do not need their own `publish` label).
|
||||
|
||||
## Theme
|
||||
|
||||
Maps, GPX cards, players and captions follow the blog's day/night toggle
|
||||
(`theme-dark` / `theme-light` class on `<html>`), **not** the OS setting. Map basemaps are
|
||||
VersaTiles `muted` ("neutron" look) in day mode, CSS-inverted at night; a subtle sage/bone
|
||||
tint overlay sits on the map in day mode only.
|
||||
|
||||
## Styling knobs (geo map render)
|
||||
|
||||
- map height: `[map x|small]` ≈ 280 px, `medium` ≈ 440 px, default `min(70vh, 640px)`, `large` ≈ 620 px (Cad: same words via `CAD_HEIGHTS`)
|
||||
- map style label on the book note: `#map:style=auto|muted|neutron|osm-bright|liberty`
|
||||
- tint: `.blog-geo-map::after` gradient + `mix-blend-mode: multiply`
|
||||
- 3D shading: append `?render=normal` to the page URL to disable the electron-microscope shader
|
||||
+14
-14
@@ -1,7 +1,7 @@
|
||||
# Switching the blog theme
|
||||
|
||||
The blog theme is **not** selected by a file name – it is controlled by the **`~shareCss` relation**
|
||||
on the blog root note (`Richard's Blog`) inside Trilium. The relation points at a `text/css` note
|
||||
on the blog root note (`Your Blog`) inside Trilium. The relation points at a `text/css` note
|
||||
that holds the theme stylesheet. Everything below uses the notes of this repository's live blog;
|
||||
note IDs are given for reference.
|
||||
|
||||
@@ -9,12 +9,12 @@ note IDs are given for reference.
|
||||
|
||||
| Theme | Repo file | Live note | Dark / light |
|
||||
|---|---|---|---|
|
||||
| **Ash (active)** | `theme/blog_share_theme_ash.css` | `OCbcTyzbQJA2` | panel/bone · ember+steel / paper+ink |
|
||||
| Coffee (default) | `theme/blog_share_theme_coffee.css` | `NqZXcFRGi3II` | espresso / latte-cream |
|
||||
| Harbor | `theme/blog_share_theme_harbor.css` | `Ep54EDTwKxup` | harbordark / paper |
|
||||
| Gruvbox | `theme/blog_share_theme_gruvbox.css` | `edhL5cByGwqA` | gruvbox dark / light |
|
||||
| Solarized | `theme/blog_share_theme_solarized.css` | `UFxPR5IpTyfj` | base03 / base3 |
|
||||
| Tokyo Night | `theme/blog_share_theme_tokyonight.css` | `bf1OKYo3msZm` | storm / day |
|
||||
| **Ash (active)** | `theme/blog_share_theme_ash.css` | `__THEME_ASH_ID__` | panel/bone · ember+steel / paper+ink |
|
||||
| Coffee (default) | `theme/blog_share_theme_coffee.css` | `__THEME_COFFEE_ID__` | espresso / latte-cream |
|
||||
| Harbor | `theme/blog_share_theme_harbor.css` | `__THEME_HARBOR_ID__` | harbordark / paper |
|
||||
| Gruvbox | `theme/blog_share_theme_gruvbox.css` | `__THEME_GRUVBOX_ID__` | gruvbox dark / light |
|
||||
| Solarized | `theme/blog_share_theme_solarized.css` | `__THEME_SOLARIZED_ID__` | base03 / base3 |
|
||||
| Tokyo Night | `theme/blog_share_theme_tokyonight.css` | `__THEME_TOKYONIGHT_ID__` | storm / day |
|
||||
|
||||
All six themes include a `html.theme-light` + `html.theme-dark` block, theme-aware **3D viewer**
|
||||
overlays and **Mermaid** rendering. The 3D viewer reads the theme's CSS variables
|
||||
@@ -23,7 +23,7 @@ automatically to whichever theme is active – no script changes needed.
|
||||
|
||||
## How the active theme is resolved
|
||||
|
||||
1. The blog root note (`Richard's Blog`, `OKjkpgmXZY1L`) carries `~shareCss` → `blog_share_theme_*.css`.
|
||||
1. The blog root note (`Your Blog`, `__BLOG_ROOT_ID__`) carries `~shareCss` → `blog_share_theme_*.css`.
|
||||
2. The target note is a `code` note with mime `text/css` living under `.blog` (the share system note).
|
||||
3. The public share renders the page and loads that note's CSS.
|
||||
|
||||
@@ -35,26 +35,26 @@ table above), so only the relation needs to change.
|
||||
### Option A – via the Trilium UI
|
||||
|
||||
1. Find the target theme note under `.blog` (e.g. `blog_share_theme_gruvbox.css`).
|
||||
2. Open `Richard's Blog` → Attributes → edit the `~shareCss` relation to point at that note.
|
||||
2. Open `Your Blog` → Attributes → edit the `~shareCss` relation to point at that note.
|
||||
3. Hard-refresh the blog (Ctrl+F5). Done.
|
||||
|
||||
### Option B – via ETAPI (scriptable)
|
||||
|
||||
```bash
|
||||
TOKEN="your-etapi-token"
|
||||
SERVER="https://notes.familie-zink.org"
|
||||
SERVER="https://YOUR-TRILIUM.example.com"
|
||||
|
||||
# 1. Delete the current shareCss relation on the blog root (OKjkpgmXZY1L)
|
||||
# 1. Delete the current shareCss relation on the blog root (__BLOG_ROOT_ID__)
|
||||
curl -X DELETE "$SERVER/etapi/attributes/<attributeId>" -H "Authorization: $TOKEN"
|
||||
|
||||
# 2. Point shareCss at the desired theme note (example: Gruvbox edhL5cByGwqA)
|
||||
# 2. Point shareCss at the desired theme note (example: Gruvbox __THEME_GRUVBOX_ID__)
|
||||
curl -X POST "$SERVER/etapi/attributes" \
|
||||
-H "Authorization: $TOKEN" -H "Content-Type: application/json" \
|
||||
-d '{"noteId":"OKjkpgmXZY1L","type":"relation","name":"shareCss","value":"edhL5cByGwqA","isInheritable":true}'
|
||||
-d '{"noteId":"__BLOG_ROOT_ID__","type":"relation","name":"shareCss","value":"__THEME_GRUVBOX_ID__","isInheritable":true}'
|
||||
```
|
||||
|
||||
The attribute ID of the old `shareCss` relation can be looked up via
|
||||
`GET $SERVER/etapi/notes/OKjkpgmXZY1L/attributes` (or the Trilium attribute list on the note).
|
||||
`GET $SERVER/etapi/notes/__BLOG_ROOT_ID__/attributes` (or the Trilium attribute list on the note).
|
||||
|
||||
### Option C – create a fresh theme note from this repo
|
||||
|
||||
|
||||
Reference in New Issue
Block a user