diff --git a/README.md b/README.md index dc14603..7dcc829 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,10 @@

-

Name des Projekts

+

Trilium Notes Branding

- Kurzbeschreibung des Projekts/Anwendung, um die es geht + Updatefeste Branding- und Theme-Anpassungen fuer TriliumNext Shared Notes

@@ -22,7 +22,189 @@

-CONTENT BEREICH +## Ueber dieses Projekt + +Dieses Repository enthaelt updatefeste Anpassungen fuer eine TriliumNext-Installation. Ziel ist, eigene Branding-Dateien, ein angepasstes Dark-Theme fuer Shared Notes, ein Hintergrundbild und kleine Komfortfunktionen per Docker-Volume einzubinden, ohne Dateien im Container-Image dauerhaft zu veraendern. + +Die Dateien liegen bewusst unter `./data/custom`, damit alle persistenten Konfigurationen und Assets an einem Ort liegen. + +## Funktionen + +- eigenes Logo fuer Shared Notes +- eigenes Favicon fuer Shared Notes und die Hauptanwendung +- eigenes Login-Logo +- eigenes PWA/App-Icon +- Dark-only Theme fuer Shared Notes +- Hintergrundbild fuer Shared Notes +- Kopierbutton fuer Codebloecke +- Seitenleiste in Shared Notes standardmaessig eingeklappt +- Burger-Menue bleibt erhalten, damit freigegebene Verzeichnisse weiter navigierbar sind +- alle Anpassungen als read-only Docker-Bind-Mounts + +## Ordnerstruktur + +```text +. +├── data/ +│ └── custom/ +│ ├── branding/ +│ │ ├── techniverse-favicon.ico +│ │ ├── techniverse-icon-180.png +│ │ ├── techniverse-icon-32.png +│ │ ├── techniverse-icon-512.png +│ │ ├── techniverse-icon-64.png +│ │ ├── techniverse-logo-256.png +│ │ ├── techniverse-logo-original.png +│ │ └── techniverse-logo.svg +│ └── share-theme/ +│ ├── custom-share-copy.js +│ ├── custom-share-dark.css +│ ├── page.ejs +│ ├── share-background.jpg +│ └── share-background.jpg.old +└── docker-compose.yaml +``` + +## Docker-Mounts + +Die relevanten Dateien werden in `docker-compose.yaml` read-only in den Container gemountet: + +```yaml +volumes: + - ./data/trilium-data:/home/node/trilium-data + - ./data/custom/share-theme/page.ejs:/usr/src/app/share-theme/templates/page.ejs:ro + - ./data/custom/share-theme/custom-share-dark.css:/usr/src/app/share-theme/assets/custom-share-dark.css:ro + - ./data/custom/share-theme/custom-share-copy.js:/usr/src/app/share-theme/assets/custom-share-copy.js:ro + - ./data/custom/share-theme/share-background.jpg:/usr/src/app/share-theme/assets/share-background.jpg:ro + - ./data/custom/branding/techniverse-logo-256.png:/usr/src/app/share-theme/assets/techniverse-logo-256.png:ro + - ./data/custom/branding/techniverse-icon-64.png:/usr/src/app/share-theme/assets/techniverse-icon-64.png:ro + - ./data/custom/branding/techniverse-logo.svg:/usr/src/app/assets/images/icon-color.svg:ro + - ./data/custom/branding/techniverse-icon-180.png:/usr/src/app/assets/images/app-icons/ios/apple-touch-icon.png:ro + - ./data/custom/branding/techniverse-icon-512.png:/usr/src/app/public/assets/icon.png:ro + - ./data/custom/branding/techniverse-favicon.ico:/usr/src/app/public/favicon.ico:ro + - ./data/custom/branding/techniverse-favicon.ico:/usr/src/app/assets/icon.ico:ro +``` + +## Shared Notes Theme + +Die Datei `data/custom/share-theme/custom-share-dark.css` ueberschreibt das Standard-Theme der TriliumNext Shared Notes. Der Lightmode wird dabei absichtlich nicht separat gepflegt: + +- `html.theme-light` und `html.theme-dark` verwenden dieselben dunklen Variablen +- `color-scheme: dark` ist gesetzt +- die Theme-Auswahl wird per CSS ausgeblendet + +Dadurch muss nur ein konsistenter Darkmode gepflegt werden. + +## Hintergrundbild + +Das Hintergrundbild liegt unter: + +```text +data/custom/share-theme/share-background.jpg +``` + +Es wird in `custom-share-dark.css` referenziert: + +```css +--mtp-share-background: url("share-background.jpg?v=20260605"); +``` + +Wenn das Bild ausgetauscht wird, kann der Browser die alte Version noch im Cache haben. In dem Fall entweder hart neu laden oder den Cache-Buster in der CSS-Datei erhoehen, zum Beispiel: + +```css +--mtp-share-background: url("share-background.jpg?v=20260605-2"); +``` + +Ein Container-Neustart ist nur noetig, wenn der Dateiname oder der Docker-Mount geaendert wird. + +## Kopierbutton fuer Codebloecke + +Die Datei `data/custom/share-theme/custom-share-copy.js` fuegt in Shared Notes an alle echten Codebloecke einen Button hinzu. + +Der Button: + +- erscheint bei `
`-Bloecken
+- kopiert den Code in die Zwischenablage
+- zeigt nach dem Klick kurz `Kopiert`
+- nutzt die Clipboard API mit Fallback fuer aeltere Browser
+
+## Seitenleiste und Burger-Menue
+
+Die Seitenleiste startet fuer neue Besucher standardmaessig eingeklappt. Das Burger-Menue bleibt sichtbar und kann die Navigation weiterhin oeffnen.
+
+Das ist wichtig fuer freigegebene Verzeichnisse, weil dort die Unterseiten ueber die Seitenleiste erreichbar sind.
+
+Technisch wird beim ersten Aufruf gesetzt:
+
+```js
+localStorage.setItem("left-pane-collapsed", "true");
+```
+
+Wenn ein Besucher die Seitenleiste oeffnet, speichert Trilium diese Entscheidung wie gewohnt im Browser.
+
+## Branding
+
+Die Branding-Dateien liegen unter:
+
+```text
+data/custom/branding/
+```
+
+Verwendete Dateien:
+
+- `techniverse-logo-256.png`: Logo in Shared Notes
+- `techniverse-icon-64.png`: Favicon fuer Shared Notes
+- `techniverse-logo.svg`: Ersatz fuer Triliums Login-Logo `icon-color.svg`
+- `techniverse-icon-180.png`: Apple-Touch-Icon
+- `techniverse-icon-512.png`: PWA/App-Icon
+- `techniverse-icon-32.png`: kleine Icon-Variante, aktuell Reserve/Quelle fuer weitere Mounts
+- `techniverse-favicon.ico`: Favicon der Hauptanwendung
+- `techniverse-logo-original.png`: Originaldatei als Quelle
+
+Die Datei `share-background.jpg.old` ist nur eine lokale Sicherung des vorherigen Hintergrundbildes und wird nicht in den Container gemountet.
+
+## Installation
+
+1. Repository auf den Docker-Host kopieren.
+2. Trilium-Daten unter `data/trilium-data` bereitstellen oder vorhandene Daten dort belassen.
+3. `docker-compose.yaml` bei Bedarf an Port, Netzwerk oder Containername anpassen.
+4. Compose-Konfiguration pruefen:
+
+```bash
+docker compose config
+```
+
+5. Container starten oder neu erstellen:
+
+```bash
+docker compose up -d
+```
+
+## Update-Hinweise
+
+Die Anpassungen sind updatefest, weil sie als read-only Volumes eingebunden werden. Beim Update des TriliumNext-Images bleiben die Dateien unter `data/custom` erhalten.
+
+Wichtig ist nur `data/custom/share-theme/page.ejs`: Diese Datei basiert auf dem TriliumNext Share-Template und enthaelt zusaetzliche Includes fuer CSS, JavaScript, Logo und Favicon. Wenn TriliumNext das Share-Template groesser veraendert, sollte die neue Upstream-Version mit dieser Datei verglichen werden.
+
+Beibehalten werden sollten:
+
+- Include fuer `custom-share-dark.css`
+- Include fuer `custom-share-copy.js`
+- Branding-Variablen fuer Logo und Favicon
+- Default-Collapse-Logik fuer die Seitenleiste
+
+## Cache-Hinweise
+
+Browser cachen CSS, Bilder und Favicons oft aggressiv. Wenn eine Aenderung nicht sichtbar ist:
+
+- Seite hart neu laden
+- Inkognito-Fenster testen
+- Cache-Buster in CSS oder Template erhoehen
+- bei geaenderten Mounts den Container neu erstellen
+
+## Dokumentation
+
+Die Projektdokumentation liegt bewusst zentral in dieser `README.md`. Zusaetzliche README-Dateien in Unterordnern wurden entfernt, damit spaeter beim Veroeffentlichen des Repositories keine widerspruechlichen oder veralteten Hinweise entstehen.
 
 
 

@@ -34,4 +216,4 @@ CONTENT BEREICH © Patrick Asmus · Techniverse Network · Lizenz -

\ No newline at end of file +

diff --git a/data/custom/branding/techniverse-favicon.ico b/data/custom/branding/techniverse-favicon.ico new file mode 100644 index 0000000..4797c34 Binary files /dev/null and b/data/custom/branding/techniverse-favicon.ico differ diff --git a/data/custom/branding/techniverse-icon-180.png b/data/custom/branding/techniverse-icon-180.png new file mode 100644 index 0000000..2f999fd Binary files /dev/null and b/data/custom/branding/techniverse-icon-180.png differ diff --git a/data/custom/branding/techniverse-icon-32.png b/data/custom/branding/techniverse-icon-32.png new file mode 100644 index 0000000..01c52f2 Binary files /dev/null and b/data/custom/branding/techniverse-icon-32.png differ diff --git a/data/custom/branding/techniverse-icon-512.png b/data/custom/branding/techniverse-icon-512.png new file mode 100644 index 0000000..2db52ce Binary files /dev/null and b/data/custom/branding/techniverse-icon-512.png differ diff --git a/data/custom/branding/techniverse-icon-64.png b/data/custom/branding/techniverse-icon-64.png new file mode 100644 index 0000000..b023cdc Binary files /dev/null and b/data/custom/branding/techniverse-icon-64.png differ diff --git a/data/custom/branding/techniverse-logo-256.png b/data/custom/branding/techniverse-logo-256.png new file mode 100644 index 0000000..0f1dee9 Binary files /dev/null and b/data/custom/branding/techniverse-logo-256.png differ diff --git a/data/custom/branding/techniverse-logo-original.png b/data/custom/branding/techniverse-logo-original.png new file mode 100644 index 0000000..647309b Binary files /dev/null and b/data/custom/branding/techniverse-logo-original.png differ diff --git a/data/custom/branding/techniverse-logo.svg b/data/custom/branding/techniverse-logo.svg new file mode 100644 index 0000000..6f6af5b --- /dev/null +++ b/data/custom/branding/techniverse-logo.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/data/custom/share-theme/custom-share-copy.js b/data/custom/share-theme/custom-share-copy.js new file mode 100644 index 0000000..63c5454 --- /dev/null +++ b/data/custom/share-theme/custom-share-copy.js @@ -0,0 +1,89 @@ +(function () { + "use strict"; + + const copiedLabel = "Kopiert"; + const copyLabel = "Kopieren"; + const resetDelayMs = 1600; + + function getCodeText(codeElement) { + return codeElement.textContent.replace(/\n$/, ""); + } + + async function copyText(text) { + if (navigator.clipboard && window.isSecureContext) { + await navigator.clipboard.writeText(text); + return; + } + + const textArea = document.createElement("textarea"); + textArea.value = text; + textArea.setAttribute("readonly", ""); + textArea.style.position = "fixed"; + textArea.style.top = "-1000px"; + textArea.style.opacity = "0"; + document.body.appendChild(textArea); + textArea.select(); + + try { + document.execCommand("copy"); + } finally { + textArea.remove(); + } + } + + function setButtonState(button, label, copied) { + button.textContent = label; + button.setAttribute("aria-label", label); + button.classList.toggle("is-copied", copied); + } + + function enhanceCodeBlock(preElement) { + if (preElement.closest(".mtp-code-block")) { + return; + } + + const codeElement = preElement.querySelector("code"); + if (!codeElement || !getCodeText(codeElement).trim()) { + return; + } + + const wrapper = document.createElement("div"); + wrapper.className = "mtp-code-block"; + preElement.parentNode.insertBefore(wrapper, preElement); + wrapper.appendChild(preElement); + + const button = document.createElement("button"); + button.type = "button"; + button.className = "mtp-copy-code-button"; + setButtonState(button, copyLabel, false); + + button.addEventListener("click", async () => { + const originalLabel = button.textContent; + button.disabled = true; + + try { + await copyText(getCodeText(codeElement)); + setButtonState(button, copiedLabel, true); + window.setTimeout(() => { + button.disabled = false; + setButtonState(button, copyLabel, false); + }, resetDelayMs); + } catch (error) { + button.disabled = false; + setButtonState(button, originalLabel || copyLabel, false); + } + }); + + wrapper.appendChild(button); + } + + function initCopyButtons() { + document.querySelectorAll("#content pre").forEach(enhanceCodeBlock); + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", initCopyButtons, { once: true }); + } else { + initCopyButtons(); + } +})(); diff --git a/data/custom/share-theme/custom-share-dark.css b/data/custom/share-theme/custom-share-dark.css new file mode 100644 index 0000000..8024252 --- /dev/null +++ b/data/custom/share-theme/custom-share-dark.css @@ -0,0 +1,333 @@ +/* + * Media-Techport TriliumNext share theme overrides. + * Mounted as a read-only Docker volume so image updates do not overwrite it. + */ + +:root { + color-scheme: dark; + --mtp-radius: 8px; + --mtp-radius-sm: 6px; + --mtp-shadow: 0 18px 48px rgb(0 0 0 / 18%); + --mtp-content-width: 980px; + --mtp-font-sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + --mtp-font-mono: "JetBrains Mono", "Cascadia Code", Consolas, monospace; + --mtp-share-background: url("share-background.jpg?v=20260605"); +} + +html.theme-light, +html.theme-dark { + --background-primary: #11110f; + --background-secondary: #191816; + --background-highlight: #2b2924; + --background-active: #d2a84a; + --text-primary: #e8e2d6; + --text-heading: #fff6e5; + --text-menu: #cfc6b8; + --text-link: #69d5c3; + --text-menu-active: #171411; +} + +html.theme-light .light-icon, +html.theme-light .dark-icon, +html.theme-dark .light-icon, +html.theme-dark .dark-icon { + display: none; +} + +body { + font-family: var(--mtp-font-sans); + letter-spacing: 0; + text-rendering: optimizeLegibility; + background: var(--background-primary); +} + +a { + color: var(--text-link); + text-underline-offset: 0.18em; +} + +a:hover { + text-decoration: underline; +} + +#header { + min-height: 56px; + backdrop-filter: blur(10px); + box-shadow: 0 1px 0 rgb(255 255 255 / 6%); +} + +#header-logo { + gap: 10px; + font-weight: 700; + letter-spacing: 0; +} + +#header-logo img { + width: 32px; + height: 32px; + object-fit: contain; + border-radius: var(--mtp-radius-sm); +} + +#left-pane { + border-right-width: 1px; + box-shadow: 12px 0 40px rgb(0 0 0 / 10%); +} + +#navigation { + gap: 18px; +} + +.theme-selection { + display: none; +} + +.search-input { + min-height: 36px; + border: 1px solid transparent; +} + +.search-input:focus { + border-color: var(--text-link); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--text-link) 22%, transparent); +} + +#menu a { + min-height: 30px; + padding: 4px 8px; + border-radius: var(--mtp-radius-sm); +} + +#menu a:hover { + background: color-mix(in srgb, var(--background-highlight) 72%, var(--text-link)); + border-color: transparent; + text-decoration: none; +} + +#menu a.active { + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--background-active) 65%, white); +} + +#right-pane { + background: + linear-gradient(90deg, rgb(17 17 15 / 72%), rgb(17 17 15 / 52%) 42%, rgb(17 17 15 / 78%)), + linear-gradient(180deg, rgb(17 17 15 / 28%), rgb(17 17 15 / 86%) 78%), + var(--mtp-share-background) center / cover fixed, + var(--background-primary); +} + +#main { + max-width: var(--mtp-content-width); + padding: 34px clamp(18px, 4vw, 56px) 42px; +} + +#content { + font-size: 1rem; + line-height: 1.72; +} + +#title { + margin-bottom: 0.85em; + padding-top: 0; + font-size: clamp(2rem, 4vw, 3.4rem); + line-height: 1.08; +} + +#content h1, +#content h2, +#content h3, +#content h4, +#content h5, +#content h6 { + margin-top: 1.45em; + margin-bottom: 0.55em; + padding-bottom: 0.25em; + line-height: 1.22; + border-bottom-color: color-mix(in srgb, var(--background-highlight) 76%, transparent); +} + +#content p, +#content ul, +#content ol { + margin-block: 0.75em; +} + +#content li + li { + margin-top: 0.25em; +} + +#content blockquote { + margin: 1.1em 0; + padding: 0.8em 1em; + color: color-mix(in srgb, var(--text-primary) 84%, var(--text-link)); + background: color-mix(in srgb, var(--background-secondary) 82%, var(--text-link)); + border-left: 4px solid var(--text-link); + border-radius: var(--mtp-radius-sm); +} + +.ck-content code, +.ck-content pre { + font-family: var(--mtp-font-mono); + background: color-mix(in srgb, var(--background-secondary) 88%, black); + border-color: color-mix(in srgb, var(--background-highlight) 70%, var(--text-link)); +} + +.ck-content code { + font-size: 0.92em; +} + +.ck-content pre { + padding: 14px 16px; + box-shadow: inset 0 1px 0 rgb(255 255 255 / 4%); +} + +.mtp-code-block { + position: relative; + margin: 1em 0; +} + +.mtp-code-block > pre { + margin: 0; + padding-top: 42px; +} + +.mtp-copy-code-button { + position: absolute; + top: 8px; + right: 8px; + z-index: 2; + min-height: 28px; + padding: 0 10px; + border: 1px solid color-mix(in srgb, var(--background-highlight) 78%, var(--text-link)); + border-radius: var(--mtp-radius-sm); + color: var(--text-menu); + background: color-mix(in srgb, var(--background-secondary) 88%, black); + font: 600 0.78rem/1 var(--mtp-font-sans); + cursor: pointer; + opacity: 0.86; + transition: + opacity 160ms ease, + color 160ms ease, + background-color 160ms ease, + border-color 160ms ease; +} + +.mtp-copy-code-button:hover, +.mtp-copy-code-button:focus-visible { + opacity: 1; + color: var(--text-heading); + background: color-mix(in srgb, var(--background-highlight) 72%, var(--text-link)); + border-color: var(--text-link); + outline: none; +} + +.mtp-copy-code-button.is-copied { + color: var(--text-menu-active); + background: var(--background-active); + border-color: var(--background-active); +} + +.ck-content table { + width: 100%; + border-collapse: collapse; + overflow: hidden; + border-radius: var(--mtp-radius-sm); +} + +.ck-content table td, +.ck-content table th { + border-color: var(--background-highlight); + padding: 0.55em 0.7em; +} + +.ck-content table th { + color: var(--text-heading); + background: var(--background-secondary); +} + +#content img { + border-radius: var(--mtp-radius); + box-shadow: var(--mtp-shadow); +} + +#childLinks { + margin-top: 2rem; + padding-top: 1.2rem; +} + +#childLinks li, +#childLinks li a { + border-radius: var(--mtp-radius-sm); +} + +#toc-pane { + border-left: 1px solid var(--background-highlight); + padding-left: 18px; +} + +#toc-pane h3 { + letter-spacing: 0.08em; + color: color-mix(in srgb, var(--text-menu) 82%, var(--text-link)); +} + +#content-footer { + margin-top: 2.5rem; + border-top: 1px solid var(--background-highlight); +} + +#content-footer .navigation a { + border-radius: var(--mtp-radius-sm); +} + +html.theme-light #header, +html.theme-light #left-pane, +html.theme-dark #header, +html.theme-dark #left-pane { + background: rgb(25 24 22 / 94%); +} + +html.theme-light .search-results, +html.theme-dark .search-results { + box-shadow: var(--mtp-shadow); +} + +html.theme-light ::selection, +html.theme-dark ::selection { + color: #11110f; + background: #69d5c3; +} + +@media (max-width: 900px) { + #main { + padding: 22px 18px 34px; + } + + #title { + font-size: clamp(1.8rem, 8vw, 2.6rem); + } + + #content { + font-size: 0.98rem; + } +} + +@media print { + #header, + #left-pane, + #toc-pane { + display: none !important; + } + + html, + body, + #split-pane, + #right-pane { + overflow: visible !important; + height: auto !important; + } + + #main { + max-width: none; + padding: 0; + } +} diff --git a/data/custom/share-theme/page.ejs b/data/custom/share-theme/page.ejs new file mode 100644 index 0000000..99cdd36 --- /dev/null +++ b/data/custom/share-theme/page.ejs @@ -0,0 +1,263 @@ + + + + <% + const hasTree = subRoot.note.hasVisibleChildren(); + + // Collect HTML snippets by location + const htmlSnippetsByLocation = {}; + for (const htmlRelation of note.getRelations("shareHtml")) { + const htmlNote = htmlRelation.targetNote; + if (htmlNote) { + let location = htmlNote.getLabelValue("shareHtmlLocation") || "content:end"; + // Default to :end if no position specified + if (!location.includes(":")) { + location = location + ":end"; + } + if (!htmlSnippetsByLocation[location]) { + htmlSnippetsByLocation[location] = []; + } + htmlSnippetsByLocation[location].push(htmlNote.getContent()); + } + } + const renderSnippets = (location) => { + const snippets = htmlSnippetsByLocation[location]; + return snippets ? snippets.join("\n") : ""; + }; + %> + <%- renderSnippets("head:start") %> + + + + <% + const customShareAssetBase = (cssToLoad.find(url => url.endsWith("assets/styles.css")) || "assets/styles.css") + .replace(/styles\.css$/, ""); + const customShareCssUrl = `${customShareAssetBase}custom-share-dark.css?v=20260605`; + const customShareCopyJsUrl = `${customShareAssetBase}custom-share-copy.js?v=20260605`; + const customShareFaviconUrl = faviconUrl.includes("favicon.ico") + ? `${customShareAssetBase}techniverse-icon-64.png?v=20260605` + : faviconUrl; + %> + + <% for (const url of cssToLoad) { %> + + <% } %> + + <% for (const url of jsToLoad) { %> + + <% } %> + + <% if (note.hasLabel("shareDisallowRobotIndexing")) { %> + + <% } %> + + <% + const pageTitle = `${note.title}${note.noteId !== subRoot.note.noteId ? ` - ${subRoot.note.title}` : ""}`; + + // Setup some key OpenGraph variables + const openGraphColor = subRoot.note.getLabelValue("shareOpenGraphColor"); + const openGraphURL = subRoot.note.getLabelValue("shareOpenGraphURL"); + const openGraphDomain = subRoot.note.getLabelValue("shareOpenGraphDomain"); + let openGraphImage = subRoot.note.getLabelValue("shareOpenGraphImage"); + // Relation takes priority and requires some altering + if (subRoot.note.hasRelation("shareOpenGraphImage")) { + openGraphImage = `api/images/${subRoot.note.getRelation("shareOpenGraphImage").value}/image.png`; + } + %> + <%= pageTitle %> + + + "> + + + + + "> + + + + + + + "> + + + + + <%- renderSnippets("head:end") %> + +<% +const logoWidth = subRoot.note.getLabelValue("shareLogoWidth") ?? 53; +const logoHeight = subRoot.note.getLabelValue("shareLogoHeight") ?? 40; +const mobileLogoHeight = logoHeight && logoWidth ? 32 / (logoWidth / logoHeight) : ""; +const customShareLogoUrl = logoUrl.includes("icon-color.svg") + ? `${customShareAssetBase}techniverse-logo-256.png?v=20260605` + : logoUrl; +const customShareLogoHeight = customShareLogoUrl === logoUrl ? mobileLogoHeight : 32; +const shareRootLink = subRoot.note.hasLabel("shareRootLink") ? subRoot.note.getLabelValue("shareRootLink") : `./${subRoot.note.noteId}`; +const headingRe = /()(.+?)(<\/h[1-6]>)/g; +const headingMatches = [...content.matchAll(headingRe)]; +content = content.replaceAll(headingRe, (...match) => { + const slug = utils.slugify(utils.stripTags(match[2])); + match[0] = match[0].replace(match[3], `#${match[3]}`); + return match[0]; +}); +%> + +<%- renderSnippets("body:start") %> + +
+
+ +
+
+
+
ck-content<% } %><% if (isEmpty) { %> no-content<% } %>"> + <%- renderSnippets("content:start") %> +

<%= note.title %>

+ <% if (isEmpty && (!note.hasVisibleChildren() && note.type !== "book")) { %> +

This note has no content.

+ <% } else { %> + <% + content = content.replace(/${t( + <%- content %> + <% } %> + <%- renderSnippets("content:end") %> +
+ + <% if (note.hasVisibleChildren() || note.type === "book") { %> + + <% } %> + +
+ <% if (!isEmpty && !isStatic) { %> +
+ <% const lastUpdated = new Date(note.utcDateModified); %> + <%- t("share_theme.last-updated", { date: ``}) %> +
+ <% } %> + + <% if (hasTree) { %> + <%- include("prev_next", { note: note, subRoot: subRoot }) %> + <% } %> +
+
+ <% + if (headingMatches.length > 1) { + const level = (m) => parseInt(m[1].replace(/[]+/g, "")); + + const toc = [ + { + level: level(headingMatches[0]), + name: headingMatches[0][2], + children: [] + } + ]; + const last = (arr = toc) => arr[arr.length - 1]; + const makeEntry = (m) => ({level: level(m), name: m[2], children: []}); + const getLevelArr = (lvl, arr = toc) => { + if (arr[0].level === lvl) return arr; + const top = last(arr); + return top.children.length ? getLevelArr(lvl, top.children) : top.children; + }; + + + for (let m = 1; m < headingMatches.length; m++) { + const target = getLevelArr(level(headingMatches[m])); + target.push(makeEntry(headingMatches[m])); + } + %> +
+

<%= t("share_theme.on-this-page") %>

+
    + <% for (const entry of toc) { %> + <%- include("toc_item", {entry}) %> + <% } %> +
+
+ <% } %> +
+
+<%- renderSnippets("body:end") %> + + diff --git a/data/custom/share-theme/share-background.jpg b/data/custom/share-theme/share-background.jpg new file mode 100644 index 0000000..ed5e446 Binary files /dev/null and b/data/custom/share-theme/share-background.jpg differ diff --git a/data/custom/share-theme/share-background.jpg.old b/data/custom/share-theme/share-background.jpg.old new file mode 100644 index 0000000..d27e85f Binary files /dev/null and b/data/custom/share-theme/share-background.jpg.old differ diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..ef8e841 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,46 @@ +--- +services: + trilium: + image: triliumnext/trilium:latest + container_name: trilium + hostname: trilium + restart: unless-stopped + + environment: + TRILIUM_DATA_DIR: /home/node/trilium-data + + ports: + - "16001:8080" + + volumes: + - ./data/trilium-data:/home/node/trilium-data + - ./data/custom/share-theme/page.ejs:/usr/src/app/share-theme/templates/page.ejs:ro + - ./data/custom/share-theme/custom-share-dark.css:/usr/src/app/share-theme/assets/custom-share-dark.css:ro + - ./data/custom/share-theme/custom-share-copy.js:/usr/src/app/share-theme/assets/custom-share-copy.js:ro + - ./data/custom/share-theme/share-background.jpg:/usr/src/app/share-theme/assets/share-background.jpg:ro + - ./data/custom/branding/techniverse-logo-256.png:/usr/src/app/share-theme/assets/techniverse-logo-256.png:ro + - ./data/custom/branding/techniverse-icon-64.png:/usr/src/app/share-theme/assets/techniverse-icon-64.png:ro + - ./data/custom/branding/techniverse-logo.svg:/usr/src/app/assets/images/icon-color.svg:ro + - ./data/custom/branding/techniverse-icon-180.png:/usr/src/app/assets/images/app-icons/ios/apple-touch-icon.png:ro + - ./data/custom/branding/techniverse-icon-512.png:/usr/src/app/public/assets/icon.png:ro + - ./data/custom/branding/techniverse-favicon.ico:/usr/src/app/public/favicon.ico:ro + - ./data/custom/branding/techniverse-favicon.ico:/usr/src/app/assets/icon.ico:ro + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + + labels: + com.centurylinklabs.watchtower.enable: "false" + + networks: + trilium_net: + ipv4_address: 172.29.37.10 + +networks: + trilium_net: + name: trilium.dockernetwork.local + driver: bridge + ipam: + config: + - subnet: 172.29.37.0/24 + gateway: 172.29.37.1 + ip_range: 172.29.37.128/25 diff --git a/docker-compose.yaml.orig b/docker-compose.yaml.orig new file mode 100644 index 0000000..110781c --- /dev/null +++ b/docker-compose.yaml.orig @@ -0,0 +1,36 @@ +--- +services: + trilium: + image: triliumnext/trilium:latest + container_name: trilium + hostname: trilium + restart: unless-stopped + + environment: + TRILIUM_DATA_DIR: /home/node/trilium-data + + ports: + - "16001:8080" + + volumes: + - ./data/trilium-data:/home/node/trilium-data + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + + labels: + com.centurylinklabs.watchtower.enable: "false" + + networks: + trilium_net: + ipv4_address: 172.29.37.10 + +networks: + trilium_net: + name: trilium.dockernetwork.local + driver: bridge + ipam: + config: + - subnet: 172.29.37.0/24 + gateway: 172.29.37.1 + ip_range: 172.29.37.128/25 + diff --git a/docker-compose.yaml.remote-current b/docker-compose.yaml.remote-current new file mode 100644 index 0000000..110781c --- /dev/null +++ b/docker-compose.yaml.remote-current @@ -0,0 +1,36 @@ +--- +services: + trilium: + image: triliumnext/trilium:latest + container_name: trilium + hostname: trilium + restart: unless-stopped + + environment: + TRILIUM_DATA_DIR: /home/node/trilium-data + + ports: + - "16001:8080" + + volumes: + - ./data/trilium-data:/home/node/trilium-data + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + + labels: + com.centurylinklabs.watchtower.enable: "false" + + networks: + trilium_net: + ipv4_address: 172.29.37.10 + +networks: + trilium_net: + name: trilium.dockernetwork.local + driver: bridge + ipam: + config: + - subnet: 172.29.37.0/24 + gateway: 172.29.37.1 + ip_range: 172.29.37.128/25 + diff --git a/noop-compose-check.yaml b/noop-compose-check.yaml new file mode 100644 index 0000000..110781c --- /dev/null +++ b/noop-compose-check.yaml @@ -0,0 +1,36 @@ +--- +services: + trilium: + image: triliumnext/trilium:latest + container_name: trilium + hostname: trilium + restart: unless-stopped + + environment: + TRILIUM_DATA_DIR: /home/node/trilium-data + + ports: + - "16001:8080" + + volumes: + - ./data/trilium-data:/home/node/trilium-data + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + + labels: + com.centurylinklabs.watchtower.enable: "false" + + networks: + trilium_net: + ipv4_address: 172.29.37.10 + +networks: + trilium_net: + name: trilium.dockernetwork.local + driver: bridge + ipam: + config: + - subnet: 172.29.37.0/24 + gateway: 172.29.37.1 + ip_range: 172.29.37.128/25 +