A visual theme editor for trip2g's default template, shipped as a self-contained custom layout — the same mechanism the Kanban board rides on. No fork, no rebuild of trip2g.
The default template is styled entirely through Pico CSS v2.1.1
--pico-* custom properties. A theme is just a set of overrides for those variables.
This editor makes that visual:
- Live preview — an
<iframe>of your real site (/) restyles in real time as you tweak sliders and color pickers. - Save — writes a
<style>:root{…}</style>block into the admin Head HTML injection via theupdateHtmlInjectionGraphQL mutation, so the default template picks it up on every page. It upserts a single injection markedtheme-editorinstead of stacking duplicates. No JS is injected into your site — only CSS variables.
The editor is one self-contained HTML file: _layouts/theme-editor.html (inline CSS/JS,
no external dependencies, CSP-safe — the only network call is the same-origin GraphQL
endpoint).
Drop it into your vault's _layouts/ folder as theme_editor.html:
curl -fsSL https://github.com/trip2g/theme_editor_template/releases/latest/download/theme_editor.html \
-o _layouts/theme_editor.htmlThen sync your vault (obsidian-sync, or however you publish).
Unlike the Kanban template, the editor does not load a JS bundle from a release — the whole app is inlined into the layout file, so the single HTML file is all you need.
Create a note with layout: theme_editor in its frontmatter (see
example-vault/theme-editor.md):
---
layout: theme_editor
title: Theme editor
route: /theme-editor
free: true
noindex: true
---Open that note while signed in as the site admin. The sign-in widget is in the top-right corner. Editing and preview work for anyone; Save requires admin (the server re-checks admin auth on the mutation).
- Type & shape tab — root variables that apply to both modes: fonts, base font size,
line height, weight, corner radius, spacing, paragraph gap, and trip2g's
--site-max-width. - Light / Dark tabs — per-mode colors (primary, background, text, cards, headings,
code, highlight, borders …). Each is written under
:root[data-theme="light"|"dark"]so light and dark theme independently. - A control with the ● marker is an active override; the × button clears it (falls back to the Pico default).
- Reset clears every override; Save then persists the empty theme (removes it site-wide).
- The Light/Dark switch above the preview flips which mode the iframe shows.
- Preview target — the path box above the preview is editable: type any same-origin
path (e.g.
/en/user/themes,/ru/user/themes, a docs page) and press Enter to point the preview there. Docs / Home preset buttons are shortcuts. External URLs are rejected (only the path is kept, resolved against the site origin). The default is/en/user/themes— a plain default-template docs page — rather than the site home, which may carry its own custom styles.
The layout is assembled from src/ by a zero-dependency Node script:
node build.mjs # or: npm run buildIt inlines src/editor.css and src/editor.js into src/theme-editor.template.html,
writing dist/_layouts/theme-editor.html.
src/
theme-editor.template.html Jet layout wrapper (head chrome + data markers)
editor.css editor styles
editor.js editor logic (plain JS, no framework)
build.mjs inliner → dist/_layouts/theme-editor.html
dist/_layouts/theme-editor.html built artifact (the thing you install)
example-vault/ sample note + layout for local testing
- Endpoint:
POST /_system/graphql,credentials: 'include'(session cookie). Same path and auth the $mol user-space bundle uses. - Load:
query { admin { allHtmlInjections { nodes { id description placement content position } } } }, then pick the node whosedescription == "theme-editor". - Save (upsert):
- new →
createHtmlInjection(input: { description: "theme-editor", position: 0, placement: "head", content }) - existing →
updateHtmlInjection(input: { id, description: "theme-editor", position: 0, placement: "head", content }) contentis the generated<style>…</style>block.placementmust be"head"(validated server-side as one ofhead|body_end); Head renders after the base stylesheet, so overrides win.
- new →
- Auth: admin. Mutations run under
admin { … }and the resolver callsCurrentAdminUserToken; a non-admin session getsunauthorized.
Static verification (no instance needed):
node build.mjs # builds the layout
node --check src/editor.jsLive round-trip (needs a trip2g instance where you have admin):
- Copy
dist/_layouts/theme-editor.htmlinto the instance vault's_layouts/. - Add a note with
layout: theme_editorand sync. - Open the note as admin, tweak a variable, watch the iframe restyle, click Save.
- Reload any other page — the theme applies site-wide.
- Check Admin → SEO & URLs → HTML Injections: there is exactly one injection named
theme-editor; saving again updates it in place (no duplicates).