This commit is contained in:
Patrick Asmus
2026-06-05 16:28:13 +02:00
parent 7f3debcd6c
commit 4bdc7ef496
18 changed files with 1028 additions and 4 deletions

190
README.md
View File

@@ -4,10 +4,10 @@
</a> </a>
</p> </p>
<h1 align="center">Name des Projekts</h1> <h1 align="center">Trilium Notes Branding</h1>
<h4 align="center"> <h4 align="center">
Kurzbeschreibung des Projekts/Anwendung, um die es geht Updatefeste Branding- und Theme-Anpassungen fuer TriliumNext Shared Notes
</h4> </h4>
<h6 align="center"> <h6 align="center">
@@ -22,7 +22,189 @@
<br><br> <br><br>
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 `<pre><code>`-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.
<br><br> <br><br>
@@ -34,4 +216,4 @@ CONTENT BEREICH
<sub> <sub>
© Patrick Asmus · Techniverse Network · <a href="./LICENSE">Lizenz</a> © Patrick Asmus · Techniverse Network · <a href="./LICENSE">Lizenz</a>
</sub> </sub>
</p> </p>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -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();
}
})();

View File

@@ -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;
}
}

View File

@@ -0,0 +1,263 @@
<!DOCTYPE html>
<html lang="en">
<head>
<%
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") %>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<%
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;
%>
<link rel="shortcut icon" href="<%= customShareFaviconUrl %>">
<% for (const url of cssToLoad) { %>
<link href="<%= url %>" rel="stylesheet">
<% } %>
<link href="<%= customShareCssUrl %>" rel="stylesheet">
<% for (const url of jsToLoad) { %>
<script type="module" src="<%= url %>"></script>
<% } %>
<script defer src="<%= customShareCopyJsUrl %>"></script>
<% if (note.hasLabel("shareDisallowRobotIndexing")) { %>
<meta name="robots" content="noindex,follow" />
<% } %>
<%
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`;
}
%>
<title><%= pageTitle %></title>
<script>
// Load dark/light theme as soon as possible to avoid color flashes.
let theme = localStorage.getItem("theme");
if (!theme) {
theme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? "dark" : "light";
}
document.documentElement.classList.add(`theme-${theme}`);
window.glob = {
isStatic: <%= !!isStatic %>,
theme
};
(function() {
const leftPaneState = localStorage.getItem("left-pane-collapsed");
const leftCollapsed = leftPaneState === null ? true : leftPaneState === "true";
const tocCollapsed = localStorage.getItem("toc-pane-collapsed") === "true";
if (leftPaneState === null) localStorage.setItem("left-pane-collapsed", "true");
if (leftCollapsed) document.documentElement.classList.add("left-pane-collapsed");
if (tocCollapsed) document.documentElement.classList.add("toc-pane-collapsed");
})();
</script>
<!-- HTML Meta Tags -->
<meta name="description" content="<%= note.getLabelValue("shareDescription") %>">
<!-- Facebook Meta Tags -->
<meta property="og:url" content="<%= openGraphURL %>">
<meta property="og:type" content="website">
<meta property="og:title" content="<%= pageTitle %>">
<meta property="og:description" content="<%= note.getLabelValue("shareDescription") %>">
<meta property="og:image" content="<%= openGraphImage %>">
<!-- Twitter Meta Tags -->
<meta name="twitter:card" content="summary_large_image">
<meta property="twitter:domain" content="<%= openGraphDomain %>">
<meta property="twitter:url" content="<%= openGraphURL %>">
<meta name="twitter:title" content="<%= pageTitle %>">
<meta name="twitter:description" content="<%= note.getLabelValue("shareDescription") %>">
<meta name="twitter:image" content="<%= openGraphImage %>">
<!-- Meta Tags Generated via https://opengraph.dev -->
<meta name="theme-color" content="<%= openGraphColor %>">
<style id="trilium-icon-packs">
<%- iconPackCss %>
</style>
<%- renderSnippets("head:end") %>
</head>
<%
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]>)(.+?)(<\/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], `<a id="${slug}" class="toc-anchor" name="${slug}" href="#${slug}">#</a>${match[3]}`);
return match[0];
});
%>
<body data-note-id="<%= note.noteId %>" class="type-<%= note.type %>" data-ancestor-note-id="<%= subRoot.note.noteId %>">
<%- renderSnippets("body:start") %>
<div id="header">
<button aria-label="Toggle Navigation" id="left-pane-toggle-button" class="header-button"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M4 6h16v2H4zm0 5h16v2H4zm0 5h16v2H4z"></path></svg></button>
<a href="<%= shareRootLink %>" id="header-logo">
<img src="<%= customShareLogoUrl %>" width="32" height="<%= customShareLogoHeight %>" alt="Logo" />
<%= subRoot.note.title %>
</a>
<% if (headingMatches.length > 1) { %>
<button aria-label="Toggle Table of Contents" id="toc-pane-toggle-button" class="header-button"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M3 9h14V7H3v2zm0 4h14v-2H3v2zm0 4h14v-2H3v2zm16 0h2v-2h-2v2zm0-10V7h2v2h-2zm0 6h2v-2h-2v2z"></path></svg></button>
<% } else { %>
<div class="header-button-placeholder"></div>
<% } %>
</div>
<div id="split-pane">
<div id="left-pane">
<div id="navigation">
<div id="site-header">
<div class="theme-selection">
<span id="sitetheme"><%= t("share_theme.site-theme") %></span>
<label class="switch">
<input type="checkbox" aria-labelledby="sitetheme">
<span class="slider"></span>
<svg class="dark-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M20.742 13.045a8.088 8.088 0 0 1-2.077.271c-2.135 0-4.14-.83-5.646-2.336a8.025 8.025 0 0 1-2.064-7.723A1 1 0 0 0 9.73 2.034a10.014 10.014 0 0 0-4.489 2.582c-3.898 3.898-3.898 10.243 0 14.143a9.937 9.937 0 0 0 7.072 2.93 9.93 9.93 0 0 0 7.07-2.929 10.007 10.007 0 0 0 2.583-4.491 1.001 1.001 0 0 0-1.224-1.224zm-2.772 4.301a7.947 7.947 0 0 1-5.656 2.343 7.953 7.953 0 0 1-5.658-2.344c-3.118-3.119-3.118-8.195 0-11.314a7.923 7.923 0 0 1 2.06-1.483 10.027 10.027 0 0 0 2.89 7.848 9.972 9.972 0 0 0 7.848 2.891 8.036 8.036 0 0 1-1.484 2.059z"></path></svg>
<svg class="light-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M6.993 12c0 2.761 2.246 5.007 5.007 5.007s5.007-2.246 5.007-5.007S14.761 6.993 12 6.993 6.993 9.239 6.993 12zM12 8.993c1.658 0 3.007 1.349 3.007 3.007S13.658 15.007 12 15.007 8.993 13.658 8.993 12 10.342 8.993 12 8.993zM10.998 19h2v3h-2zm0-17h2v3h-2zm-9 9h3v2h-3zm17 0h3v2h-3zM4.219 18.363l2.12-2.122 1.415 1.414-2.12 2.122zM16.24 6.344l2.122-2.122 1.414 1.414-2.122 2.122zM6.342 7.759 4.22 5.637l1.415-1.414 2.12 2.122zm13.434 10.605-1.414 1.414-2.122-2.122 1.414-1.414z"></path></svg>
</label>
<script>
const el = document.querySelector(".theme-selection input");
el.checked = (glob.theme === "dark");
</script>
</div>
<% if (hasTree) { %>
<div class="search-item">
<svg class="search-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M10 18a7.952 7.952 0 0 0 4.897-1.688l4.396 4.396 1.414-1.414-4.396-4.396A7.952 7.952 0 0 0 18 10c0-4.411-3.589-8-8-8s-8 3.589-8 8 3.589 8 8 8zm0-14c3.309 0 6 2.691 6 6s-2.691 6-6 6-6-2.691-6-6 2.691-6 6-6z"></path></svg>
<input type="text" class="search-input" placeholder="<%= t("share_theme.search_placeholder") %>">
</div>
<% } %>
</div>
<% if (hasTree) { %>
<nav id="menu">
<%- include("tree_item", {note: subRoot.note, activeNote: note, subRoot: subRoot, ancestors}) %>
</nav>
<% } %>
</div>
</div>
<div id="right-pane">
<div id="main">
<div id="content" class="type-<%= note.type %><% if (note.type === "text") { %> ck-content<% } %><% if (isEmpty) { %> no-content<% } %>">
<%- renderSnippets("content:start") %>
<h1 id="title"><%= note.title %></h1>
<% if (isEmpty && (!note.hasVisibleChildren() && note.type !== "book")) { %>
<p>This note has no content.</p>
<% } else { %>
<%
content = content.replace(/<img /g, `<img alt="${t("share_theme.image_alt")}" loading="lazy" `);
%>
<%- content %>
<% } %>
<%- renderSnippets("content:end") %>
</div>
<% if (note.hasVisibleChildren() || note.type === "book") { %>
<nav id="childLinks" class="<% if (isEmpty) { %>grid<% } else { %>list<% } %>">
<% if (!isEmpty) { %>
<span><%= t("share_theme.subpages") %></span>
<% } %>
<ul>
<%
const action = note.type === "book" ? "getChildNotes" : "getVisibleChildNotes";
for (const childNote of note[action]()) {
const isExternalLink = childNote.hasLabel("shareExternal") || childNote.hasLabel("shareExternalLink");
const linkHref = isExternalLink ? childNote.getLabelValue("shareExternal") ?? childNote.getLabelValue("shareExternalLink") : `./${childNote.shareId}`;
const target = isExternalLink ? ` target="_blank" rel="noopener noreferrer"` : "";
%>
<li>
<a class="type-<%= childNote.type %>" href="<%= linkHref %>"<%= target %>><%= childNote.title %></a>
</li>
<% } %>
</ul>
</nav>
<% } %>
<footer id="content-footer">
<% if (!isEmpty && !isStatic) { %>
<div class="updated">
<% const lastUpdated = new Date(note.utcDateModified); %>
<%- t("share_theme.last-updated", { date: `<time datetime="${lastUpdated.toISOString()}">${lastUpdated.toLocaleDateString()}</time>`}) %>
</div>
<% } %>
<% if (hasTree) { %>
<%- include("prev_next", { note: note, subRoot: subRoot }) %>
<% } %>
</footer>
</div>
<%
if (headingMatches.length > 1) {
const level = (m) => parseInt(m[1].replace(/[<h>]+/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]));
}
%>
<div id="toc-pane">
<h3><%= t("share_theme.on-this-page") %></h3>
<ul id="toc">
<% for (const entry of toc) { %>
<%- include("toc_item", {entry}) %>
<% } %>
</ul>
</div>
<% } %>
</div>
</div>
<%- renderSnippets("body:end") %>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

46
docker-compose.yaml Normal file
View File

@@ -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

36
docker-compose.yaml.orig Normal file
View File

@@ -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

View File

@@ -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

36
noop-compose-check.yaml Normal file
View File

@@ -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