Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements

This commit is contained in:
Adorian Doran 2025-11-05 17:35:10 +02:00
commit d5cb6a86c8
200 changed files with 4891 additions and 2782 deletions

View File

@ -41,7 +41,7 @@
"@types/node": "24.10.0",
"@types/yargs": "17.0.34",
"@vitest/coverage-v8": "3.2.4",
"eslint": "9.39.0",
"eslint": "9.39.1",
"eslint-plugin-simple-import-sort": "12.1.1",
"esm": "3.2.25",
"jsdoc": "4.0.5",

View File

@ -11,7 +11,7 @@
"license": "AGPL-3.0-only",
"packageManager": "pnpm@10.20.0",
"devDependencies": {
"@redocly/cli": "2.10.0",
"@redocly/cli": "2.11.0",
"archiver": "7.0.1",
"fs-extra": "11.3.2",
"react": "19.2.0",

View File

@ -22,8 +22,9 @@ async function main() {
buildSwagger(context);
buildScriptApi(context);
// Copy index file.
// Copy index and 404 files.
cpSync(join(__dirname, "index.html"), join(context.baseDir, "index.html"));
cpSync(join(context.baseDir, "user-guide/404.html"), join(context.baseDir, "404.html"));
}
main();

View File

@ -15,7 +15,7 @@
"circular-deps": "dpdm -T src/**/*.ts --tree=false --warning=false --skip-dynamic-imports=circular"
},
"dependencies": {
"@eslint/js": "9.39.0",
"@eslint/js": "9.39.1",
"@excalidraw/excalidraw": "0.18.0",
"@fullcalendar/core": "6.1.19",
"@fullcalendar/daygrid": "6.1.19",
@ -59,7 +59,7 @@
"normalize.css": "8.0.1",
"panzoom": "9.4.3",
"preact": "10.27.2",
"react-i18next": "16.2.3",
"react-i18next": "16.2.4",
"reveal.js": "5.2.1",
"svg-pan-zoom": "3.6.2",
"tabulator-tables": "6.3.1",

View File

@ -137,7 +137,7 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
command: "editBranchPrefix",
keyboardShortcut: "editBranchPrefix",
uiIcon: "bx bx-rename",
enabled: isNotRoot && parentNotSearch && noSelectedNotes && notOptionsOrHelp
enabled: isNotRoot && parentNotSearch && notOptionsOrHelp
},
{ title: t("tree-context-menu.convert-to-attachment"), command: "convertNoteToAttachment", uiIcon: "bx bx-paperclip", enabled: isNotRoot && !isHoisted && notOptionsOrHelp },

View File

@ -159,7 +159,7 @@ describe("shortcuts", () => {
expect(matchesShortcut(event, "Shift+F1")).toBeTruthy();
// Special keys
for (const keyCode of [ "Delete", "Enter" ]) {
for (const keyCode of [ "Delete", "Enter", "NumpadEnter" ]) {
event = createKeyboardEvent({ key: keyCode, code: keyCode });
expect(matchesShortcut(event, keyCode), `Key ${keyCode}`).toBeTruthy();
}

View File

@ -46,6 +46,7 @@ for (let i = 1; i <= 19; i++) {
const KEYCODES_WITH_NO_MODIFIER = new Set([
"Delete",
"Enter",
"NumpadEnter",
...functionKeyCodes
]);

View File

@ -36,10 +36,13 @@
},
"branch_prefix": {
"edit_branch_prefix": "Edit branch prefix",
"edit_branch_prefix_multiple": "Edit branch prefix for {{count}} branches",
"help_on_tree_prefix": "Help on Tree prefix",
"prefix": "Prefix: ",
"save": "Save",
"branch_prefix_saved": "Branch prefix has been saved."
"branch_prefix_saved": "Branch prefix has been saved.",
"branch_prefix_saved_multiple": "Branch prefix has been saved for {{count}} branches.",
"affected_branches": "Affected branches ({{count}}):"
},
"bulk_actions": {
"bulk_actions": "Bulk actions",

View File

@ -109,7 +109,8 @@
"export_type_single": "Solo questa nota, senza le sottostanti",
"format_opml": "OPML - formato per scambio informazioni outline. Formattazione, immagini e files non sono inclusi.",
"opml_version_1": "OPML v.1.0 - solo testo semplice",
"opml_version_2": "OPML v2.0 - supporta anche HTML"
"opml_version_2": "OPML v2.0 - supporta anche HTML",
"share-format": "HTML per la pubblicazione sul web - utilizza lo stesso tema utilizzato per le note condivise, ma può essere pubblicato come sito web statico."
},
"password_not_set": {
"body1": "Le note protette sono crittografate utilizzando una password utente, ma la password non è stata ancora impostata.",

View File

@ -0,0 +1,13 @@
.branch-prefix-dialog .branch-prefix-notes-list {
margin-top: 10px;
}
.branch-prefix-dialog .branch-prefix-notes-list ul {
max-height: 200px;
overflow: auto;
margin-top: 5px;
}
.branch-prefix-dialog .branch-prefix-current {
opacity: 0.6;
}

View File

@ -10,53 +10,86 @@ import Button from "../react/Button.jsx";
import FormGroup from "../react/FormGroup.js";
import { useTriliumEvent } from "../react/hooks.jsx";
import FBranch from "../../entities/fbranch.js";
import type { ContextMenuCommandData } from "../../components/app_context.js";
import "./branch_prefix.css";
// Virtual branches (e.g., from search results) start with this prefix
const VIRTUAL_BRANCH_PREFIX = "virt-";
export default function BranchPrefixDialog() {
const [ shown, setShown ] = useState(false);
const [ branch, setBranch ] = useState<FBranch>();
const [ branches, setBranches ] = useState<FBranch[]>([]);
const [ prefix, setPrefix ] = useState("");
const branchInput = useRef<HTMLInputElement>(null);
useTriliumEvent("editBranchPrefix", async () => {
const notePath = appContext.tabManager.getActiveContextNotePath();
if (!notePath) {
useTriliumEvent("editBranchPrefix", async (data?: ContextMenuCommandData) => {
let branchIds: string[] = [];
if (data?.selectedOrActiveBranchIds && data.selectedOrActiveBranchIds.length > 0) {
// Multi-select mode from tree context menu
branchIds = data.selectedOrActiveBranchIds.filter((branchId) => !branchId.startsWith(VIRTUAL_BRANCH_PREFIX));
} else {
// Single branch mode from keyboard shortcut or when no selection
const notePath = appContext.tabManager.getActiveContextNotePath();
if (!notePath) {
return;
}
const { noteId, parentNoteId } = tree.getNoteIdAndParentIdFromUrl(notePath);
if (!noteId || !parentNoteId) {
return;
}
const branchId = await froca.getBranchId(parentNoteId, noteId);
if (!branchId) {
return;
}
const parentNote = await froca.getNote(parentNoteId);
if (!parentNote || parentNote.type === "search") {
return;
}
branchIds = [branchId];
}
if (branchIds.length === 0) {
return;
}
const { noteId, parentNoteId } = tree.getNoteIdAndParentIdFromUrl(notePath);
const newBranches = branchIds
.map(id => froca.getBranch(id))
.filter((branch): branch is FBranch => branch !== null);
if (!noteId || !parentNoteId) {
if (newBranches.length === 0) {
return;
}
const newBranchId = await froca.getBranchId(parentNoteId, noteId);
if (!newBranchId) {
return;
}
const parentNote = await froca.getNote(parentNoteId);
if (!parentNote || parentNote.type === "search") {
return;
}
const newBranch = froca.getBranch(newBranchId);
setBranch(newBranch);
setPrefix(newBranch?.prefix ?? "");
setBranches(newBranches);
// Use the prefix of the first branch as the initial value
setPrefix(newBranches[0]?.prefix ?? "");
setShown(true);
});
async function onSubmit() {
if (!branch) {
if (branches.length === 0) {
return;
}
savePrefix(branch.branchId, prefix);
if (branches.length === 1) {
await savePrefix(branches[0].branchId, prefix);
} else {
await savePrefixBatch(branches.map(b => b.branchId), prefix);
}
setShown(false);
}
const isSingleBranch = branches.length === 1;
return (
<Modal
className="branch-prefix-dialog"
title={t("branch_prefix.edit_branch_prefix")}
title={isSingleBranch ? t("branch_prefix.edit_branch_prefix") : t("branch_prefix.edit_branch_prefix_multiple", { count: branches.length })}
size="lg"
onShown={() => branchInput.current?.focus()}
onHidden={() => setShown(false)}
@ -69,9 +102,27 @@ export default function BranchPrefixDialog() {
<div class="input-group">
<input class="branch-prefix-input form-control" value={prefix} ref={branchInput}
onChange={(e) => setPrefix((e.target as HTMLInputElement).value)} />
<div class="branch-prefix-note-title input-group-text"> - {branch && branch.getNoteFromCache().title}</div>
{isSingleBranch && branches[0] && (
<div class="branch-prefix-note-title input-group-text"> - {branches[0].getNoteFromCache().title}</div>
)}
</div>
</FormGroup>
{!isSingleBranch && (
<div className="branch-prefix-notes-list">
<strong>{t("branch_prefix.affected_branches", { count: branches.length })}</strong>
<ul>
{branches.map((branch) => {
const note = branch.getNoteFromCache();
return (
<li key={branch.branchId}>
{branch.prefix && <span className="branch-prefix-current">{branch.prefix} - </span>}
{note.title}
</li>
);
})}
</ul>
</div>
)}
</Modal>
);
}
@ -80,3 +131,8 @@ async function savePrefix(branchId: string, prefix: string) {
await server.put(`branches/${branchId}/set-prefix`, { prefix: prefix });
toast.showMessage(t("branch_prefix.branch_prefix_saved"));
}
async function savePrefixBatch(branchIds: string[], prefix: string) {
await server.put("branches/set-prefix-batch", { branchIds, prefix });
toast.showMessage(t("branch_prefix.branch_prefix_saved_multiple", { count: branchIds.length }));
}

View File

@ -1591,6 +1591,20 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
this.clearSelectedNodes();
}
async editBranchPrefixCommand({ node }: CommandListenerData<"editBranchPrefix">) {
const branchIds = this.getSelectedOrActiveBranchIds(node).filter((branchId) => !branchId.startsWith("virt-"));
if (!branchIds.length) {
return;
}
// Trigger the event with the selected branch IDs
appContext.triggerEvent("editBranchPrefix", {
selectedOrActiveBranchIds: branchIds,
node: node
});
}
canBeMovedUpOrDown(node: Fancytree.FancytreeNode) {
if (node.data.noteId === "root") {
return false;

View File

@ -13,8 +13,8 @@ export default function EditedNotesTab({ note }: TabContext) {
useEffect(() => {
if (!note) return;
server.get<EditedNotesResponse>(`edited-notes/${note.getLabelValue("dateNote")}`).then(async editedNotes => {
editedNotes = editedNotes.filter((n) => n.noteId !== note.noteId);
const noteIds = editedNotes.flatMap((n) => n.noteId);
editedNotes = editedNotes.filter((n) => n.noteId !== note.noteId);
const noteIds = editedNotes.flatMap((n) => n.noteId);
await froca.getNotes(noteIds, true); // preload all at once
setEditedNotes(editedNotes);
});
@ -41,11 +41,11 @@ export default function EditedNotesTab({ note }: TabContext) {
)}
</span>
)
}))}
}), " ")}
</div>
) : (
<div className="no-edited-notes-found">{t("edited_notes.no_edited_notes_found")}</div>
)}
</div>
)
)
}

View File

@ -72,8 +72,8 @@ function EditorFeatures() {
return (
<OptionsSection title={t("editorfeatures.title")}>
<EditorFeature name="emoji-completion-enabled" optionName="textNoteEmojiCompletionEnabled" label={t("editorfeatures.emoji_completion_enabled")} description={t("editorfeatures.emoji_completion_description")} />
<EditorFeature name="note-completion-enabled" optionName="textNoteCompletionEnabled" label={t("editorfeatures.note_completion_enabled")} description={t("editorfeatures.emoji_completion_description")} />
<EditorFeature name="slash-commands-enabled" optionName="textNoteSlashCommandsEnabled" label={t("editorfeatures.slash_commands_enabled")} description={t("editorfeatures.emoji_completion_description")} />
<EditorFeature name="note-completion-enabled" optionName="textNoteCompletionEnabled" label={t("editorfeatures.note_completion_enabled")} description={t("editorfeatures.note_completion_description")} />
<EditorFeature name="slash-commands-enabled" optionName="textNoteSlashCommandsEnabled" label={t("editorfeatures.slash_commands_enabled")} description={t("editorfeatures.slash_commands_description")} />
</OptionsSection>
);
}

View File

@ -1,6 +1,6 @@
{
"formatVersion": 2,
"appVersion": "0.99.2",
"appVersion": "0.99.3",
"files": [
{
"isClone": false,
@ -2700,6 +2700,7 @@
}
],
"format": "html",
"dataFileName": "Note Types.html",
"attachments": [],
"dirFileName": "Note Types",
"children": [

View File

@ -270,7 +270,7 @@
</li>
</ul>
</li>
<li>Note Types
<li><a href="root/Trilium%20Demo/Note%20Types.html" target="detail">Note Types</a>
<ul>
<li><a href="root/Trilium%20Demo/Note%20Types/Canvas.json" target="detail">Canvas</a>
</li>

View File

@ -14,6 +14,7 @@
<div class="ck-content">
<h2>☑️ Tasks</h2>
<ul>
<li data-list-item-id="e4b26220d6ce48997f1116dc1d1d83dc0">[…]</li>
</ul>

View File

@ -14,11 +14,10 @@
<div class="ck-content">
<figure class="image image-style-align-right image_resized" style="width:29.84%;">
<img style="aspect-ratio:150/150;" src="Trilium Demo_icon-color.svg" width="150"
height="150">
<img style="aspect-ratio:150/150;" src="Trilium Demo_icon-color.svg"
width="150" height="150">
</figure>
<p><strong>Welcome to Trilium Notes!</strong>
</p>
<p>This is a "demo" document packaged with Trilium to showcase some of its
features and also give you some ideas on how you might structure your notes.
@ -26,22 +25,17 @@
you wish.</p>
<p>If you need any help, visit <a href="https://triliumnotes.org">triliumnotes.org</a> or
our <a href="https://github.com/TriliumNext">GitHub repository</a>
</p>
<h2>Cleanup</h2>
<p>Once you're finished with experimenting and want to cleanup these pages,
you can simply delete them all.</p>
<h2>Formatting</h2>
<p>Trilium supports classic formatting like <em>italic</em>, <strong>bold</strong>, <em><strong>bold and italic</strong></em>.
You can add links pointing to <a href="https://triliumnotes.org/">external pages</a> or&nbsp;
<a
class="reference-link" href="Trilium%20Demo/Formatting%20examples">Formatting examples</a>.</p>
<h3>Lists</h3>
<p><strong>Ordered:</strong>
</p>
<ol>
<li data-list-item-id="e877cc655d0239b8bb0f38696ad5d8abb">First Item</li>
@ -56,7 +50,6 @@
</li>
</ol>
<p><strong>Unordered:</strong>
</p>
<ul>
<li data-list-item-id="e68bf4b518a16671c314a72073c3d900a">Item</li>
@ -67,7 +60,6 @@
</li>
</ul>
<h3>Block quotes</h3>
<blockquote>
<p>Whereof one cannot speak, thereof one must be silent”</p>
<p> Ludwig Wittgenstein</p>
@ -75,9 +67,9 @@
<hr>
<p>See also other examples like <a href="Trilium%20Demo/Formatting%20examples/School%20schedule.html">tables</a>,
<a
href="Trilium%20Demo/Formatting%20examples/Checkbox%20lists.html">checkbox lists,</a> <a href="Trilium%20Demo/Formatting%20examples/Highlighting.html">highlighting</a>,
href="Trilium%20Demo/Formatting%20examples/Checkbox%20lists.html">checkbox lists,</a> <a href="Trilium%20Demo/Formatting%20examples/Highlighting.html">highlighting</a>, <a href="Trilium%20Demo/Formatting%20examples/Code%20blocks.html">code blocks</a>and
<a
href="Trilium%20Demo/Formatting%20examples/Code%20blocks.html">code blocks</a>and <a href="Trilium%20Demo/Formatting%20examples/Math.html">math examples</a>.</p>
href="Trilium%20Demo/Formatting%20examples/Math.html">math examples</a>.</p>
</div>
</div>
</body>

View File

@ -21,8 +21,12 @@
language, should that fail it is possible to manually adjust it. The color
scheme for the syntax highlighting is adjustable in settings.&nbsp;</p><pre><code class="language-application-javascript-env-frontend">function helloWorld() {
alert("Hello world");
}</code></pre>
<p>For larger pieces of code it is better to use a code note, which uses
a fully-fledged code editor (CodeMirror). For an example of a code note,

View File

@ -0,0 +1,21 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="../../style.css">
<base target="_parent">
<title data-trilium-title>Note Types</title>
</head>
<body>
<div class="content">
<h1 data-trilium-h1>Note Types</h1>
<div class="ck-content">
<p>T</p>
</div>
</div>
</body>
</html>

View File

@ -13,9 +13,8 @@
<h1 data-trilium-h1>Task manager</h1>
<div class="ck-content">
<p>This is a simple TODO/Task manager. You can see some description and explanation
here: <a href="https://github.com/zadam/trilium/wiki/Task-manager">https://github.com/zadam/trilium/wiki/Task-manager</a>
</p>
<p>This is a simple TODO/Task manager. See the <a href="https://docs.triliumnotes.org/user-guide/advanced-usage/advanced-showcases/task-manager">Trilium documentation</a> for
information on how it works.</p>
<p>Please note that this is meant as scripting example only and feature/bug
support is very limited.</p>
</div>

View File

@ -16,18 +16,32 @@
<p>Documentation: <a href="http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_09_02.html">http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_09_02.html</a>
</p><pre><code class="language-text-x-sh">#!/bin/bash
# This script opens 4 terminal windows.
i="0"
while [ $i -lt 4 ]
do
xterm &amp;
i=$[$i+1]
done</code></pre>
</div>
</div>

View File

@ -17,6 +17,6 @@
},
"scripts": {
"edit-docs": "cross-env TRILIUM_PORT=37741 TRILIUM_DATA_DIR=data TRILIUM_INTEGRATION_TEST=memory-no-store DOCS_ROOT=../../../docs USER_GUIDE_ROOT=\"../../server/src/assets/doc_notes/en/User Guide\" tsx ../../scripts/electron-start.mts src/edit-docs.ts",
"edit-demo": "cross-env TRILIUM_PORT=37741 TRILIUM_DATA_DIR=data TRILIUM_INTEGRATION_TEST=memory-no-store DOCS_ROOT=../../../docs USER_GUIDE_ROOT=\"../../server/src/assets/doc_notes/en/User Guide\" tsx ../../scripts/electron-start.mts src/edit-demo.ts"
"edit-demo": "cross-env TRILIUM_PORT=37744 TRILIUM_DATA_DIR=data TRILIUM_INTEGRATION_TEST=memory-no-store DOCS_ROOT=../../../docs USER_GUIDE_ROOT=\"../../server/src/assets/doc_notes/en/User Guide\" tsx ../../scripts/electron-start.mts src/edit-demo.ts"
}
}

View File

@ -67,7 +67,7 @@
"@types/xml2js": "0.4.14",
"archiver": "7.0.1",
"async-mutex": "0.5.0",
"axios": "1.13.1",
"axios": "1.13.2",
"bindings": "1.5.0",
"bootstrap": "5.3.8",
"chardet": "2.1.1",
@ -110,12 +110,12 @@
"multer": "2.0.2",
"normalize-strings": "1.1.1",
"ollama": "0.6.2",
"openai": "6.7.0",
"openai": "6.8.0",
"rand-token": "1.0.1",
"safe-compare": "1.1.4",
"sanitize-filename": "1.6.3",
"sanitize-html": "2.17.0",
"sax": "1.4.1",
"sax": "1.4.3",
"serve-favicon": "2.5.1",
"stream-throttle": "0.1.3",
"strip-bom": "5.0.0",

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -19,8 +19,7 @@ class="image image_resized" style="width:74.04%;">
<img style="aspect-ratio:1911/997;" src="1_AI_image.png"
width="1911" height="997">
</figure>
<h2>Embeddings</h2>
<h2>Embeddings</h2>
<p><strong>Embeddings</strong> are important as it allows us to have an compact
AI “summary” (it's not human readable text) of each of your Notes, that
we can then perform mathematical functions on (such as cosine similarity)
@ -36,7 +35,7 @@ class="image image_resized" style="width:74.04%;">
<p>To see what embedding models Ollama has available, you can check out
<a
href="https://ollama.com/search?c=embedding">this search</a>on their website, and then <code>pull</code> whichever one
you want to try out. As of 4/15/25, my personal favorite is <code>mxbai-embed-large</code>.</p>
you want to try out. A popular choice is <code>mxbai-embed-large</code>.</p>
<p>First, we'll need to select the Ollama provider from the tabs of providers,
then we will enter in the Base URL for our Ollama. Since our Ollama is
running on our local machine, our Base URL is <code>http://localhost:11434</code>.
@ -80,59 +79,59 @@ class="image image_resized" style="width:74.04%;">
<p>These are the tools that currently exist, and will certainly be updated
to be more effectively (and even more to be added!):</p>
<ul>
<li><code>search_notes</code>
<li data-list-item-id="e56ad2313afb3f2f5162bcb3d9bf8e963"><code>search_notes</code>
<ul>
<li>Semantic search</li>
<li data-list-item-id="ee3b5cc2bd60f496e7919c63def713108">Semantic search</li>
</ul>
</li>
<li><code>keyword_search</code>
<li data-list-item-id="e9abe7bae428aff060dfc70ef669bd079"><code>keyword_search</code>
<ul>
<li>Keyword-based search</li>
<li data-list-item-id="ea8e2cece0d711e80eacfdb42e5d0edc3">Keyword-based search</li>
</ul>
</li>
<li><code>attribute_search</code>
<li data-list-item-id="e775735113778afe2073695881679540b"><code>attribute_search</code>
<ul>
<li>Attribute-specific search</li>
<li data-list-item-id="e64a93997e8ae8cc348e222f926866921">Attribute-specific search</li>
</ul>
</li>
<li><code>search_suggestion</code>
<li data-list-item-id="e58d04b2e226a143a8c8941337b04113a"><code>search_suggestion</code>
<ul>
<li>Search syntax helper</li>
<li data-list-item-id="e7c1ed076f0e0e3c242ca659276576860">Search syntax helper</li>
</ul>
</li>
<li><code>read_note</code>
<li data-list-item-id="e30741593cef85e71179301c280063bc2"><code>read_note</code>
<ul>
<li>Read note content (helps the LLM read Notes)</li>
<li data-list-item-id="e9c50f6fe0b4f013f16f9b32dbf6dbc92">Read note content (helps the LLM read Notes)</li>
</ul>
</li>
<li><code>create_note</code>
<li data-list-item-id="ec71fd71ce58b9d8338bc66e7c99ae1cb"><code>create_note</code>
<ul>
<li>Create a Note</li>
<li data-list-item-id="e0b0bae68b45bb0c27bc092145452ecd1">Create a Note</li>
</ul>
</li>
<li><code>update_note</code>
<li data-list-item-id="e7e816d84f52cf76096d2b5f1e4665989"><code>update_note</code>
<ul>
<li>Update a Note</li>
<li data-list-item-id="ed28fc8ca5a4753c1b680ee52d140940a">Update a Note</li>
</ul>
</li>
<li><code>manage_attributes</code>
<li data-list-item-id="e4580152b092501aa4fa87d1562b0f304"><code>manage_attributes</code>
<ul>
<li>Manage attributes on a Note</li>
<li data-list-item-id="ea0601e29b574cf0e4c40ae0e7d60bfa4">Manage attributes on a Note</li>
</ul>
</li>
<li><code>manage_relationships</code>
<li data-list-item-id="ecea045d4312b04ea06ac12802efb0544"><code>manage_relationships</code>
<ul>
<li>Manage the various relationships between Notes</li>
<li data-list-item-id="e3293a7c119de1a0eb2ddf8779a0b0d65">Manage the various relationships between Notes</li>
</ul>
</li>
<li><code>extract_content</code>
<li data-list-item-id="e483a2ef5f76d4da426c6059ddd3827a0"><code>extract_content</code>
<ul>
<li>Used to smartly extract content from a Note</li>
<li data-list-item-id="e1735854ab39d6d4e06a242bc2f80b839">Used to smartly extract content from a Note</li>
</ul>
</li>
<li><code>calendar_integration</code>
<li data-list-item-id="e11df70f4a6846bf881cb110c2950065f"><code>calendar_integration</code>
<ul>
<li>Used to find date notes, create date notes, get the daily note, etc.</li>
<li data-list-item-id="e0f1dfa6c5520bf02e125c1932b4f6e5e">Used to find date notes, create date notes, get the daily note, etc.</li>
</ul>
</li>
</ul>
@ -145,17 +144,18 @@ class="image image_resized" style="width:74.04%;">
<p>You don't need to tell the LLM to execute a certain tool, it should “smartly”
call tools and automatically execute them as needed.</p>
<h2>Overview</h2>
<p>Now that you know about embeddings and tools, you can just go ahead and
use the “Chat with Notes” button, where you can go ahead and start chatting!:</p>
<figure
class="image image_resized" style="width:60.77%;">
<p>To start, simply press the <em>Chat with Notes</em> button in the&nbsp;
<a
class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_xYmIYSP6wE3F">Launch Bar</a>.</p>
<figure class="image image_resized" style="width:60.77%;">
<img style="aspect-ratio:1378/539;" src="2_AI_image.png"
width="1378" height="539">
</figure>
<p>If you don't see the “Chat with Notes” button on your side launchbar,
you might need to move it from the “Available Launchers” section to the
“Visible Launchers” section:</p>
<figure class="image image_resized" style="width:69.81%;">
<img style="aspect-ratio:1765/1287;" src="9_AI_image.png"
width="1765" height="1287">
</figure>
</figure>
<p>If you don't see the button in the&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_xYmIYSP6wE3F">Launch Bar</a>,
you might need to move it from the <em>Available Launchers</em> section to
the <em>Visible Launchers</em> section:</p>
<figure class="image image_resized"
style="width:69.81%;">
<img style="aspect-ratio:1765/1287;" src="9_AI_image.png"
width="1765" height="1287">
</figure>

View File

@ -8,7 +8,7 @@
<li>
<p><a class="reference-link" href="#root/_help_HI6GBBIduIgv">Labels</a>&nbsp;can
be used for a variety of purposes, such as storing metadata or configuring
the behaviour of notes. Labels are also searchable, enhancing note retrieval.</p>
the behavior of notes. Labels are also searchable, enhancing note retrieval.</p>
<p>For more information, including predefined labels, see&nbsp;<a class="reference-link"
href="#root/_help_HI6GBBIduIgv">Labels</a>.</p>
</li>
@ -21,7 +21,7 @@
class="reference-link" href="#root/_help_Cq5X6iKQop6R">Relations</a>.</p>
</li>
</ol>
<p>These attributes play a crucial role in organizing, categorising, and
<p>These attributes play a crucial role in organizing, categorizing, and
enhancing the functionality of notes.</p>
<h2>Viewing the list of attributes</h2>
<p>Both the labels and relations for the current note are displayed in the <em>Owned Attributes</em> section

View File

@ -11,7 +11,7 @@ const {secret, title, content} = req.body;
if (req.method == 'POST' &amp;&amp; secret === 'secret-password') {
// notes must be saved somewhere in the tree hierarchy specified by a parent note.
// This is defined by a relation from this code note to the "target" parent note
// alternetively you can just use constant noteId for simplicity (get that from "Note Info" dialog of the desired parent note)
// alternatively you can just use constant noteId for simplicity (get that from "Note Info" dialog of the desired parent note)
const targetParentNoteId = api.currentNote.getRelationValue('targetNote');
const {note} = api.createTextNote(targetParentNoteId, title, content);
@ -30,7 +30,7 @@ else {
be saved</li>
</ul>
<h3>Explanation</h3>
<p>Let's test this by using an HTTP client to send a request:</p><pre><code class="language-text-x-trilium-auto">POST http://my.trilium.org/custom/create-note
<p>Let's test this by using an HTTP client to send a request:</p><pre><code class="language-text-x-trilium-auto">POST http://your-trilium-server/custom/create-note
Content-Type: application/json
{
@ -64,12 +64,12 @@ Content-Type: application/json
can always look into its <a href="https://expressjs.com/en/api.html">documentation</a> for
details.</p>
<h3>Parameters</h3>
<p>REST request paths often contain parameters in the URL, e.g.:</p><pre><code class="language-text-x-trilium-auto">http://my.trilium.org/custom/notes/123</code></pre>
<p>REST request paths often contain parameters in the URL, e.g.:</p><pre><code class="language-text-x-trilium-auto">http://your-trilium-server/custom/notes/123</code></pre>
<p>The last part is dynamic so the matching of the URL must also be dynamic
- for this reason the matching is done with regular expressions. Following <code>customRequestHandler</code> value
would match it:</p><pre><code class="language-text-x-trilium-auto">notes/([0-9]+)</code></pre>
<p>Additionally, this also defines a matching group with the use of parenthesis
which then makes it easier to extract the value. The matched groups are
available in <code>api.pathParams</code>:</p><pre><code class="language-text-x-trilium-auto">const noteId = api.pathParams[0];</code></pre>
<p>Often you also need query params (as in e.g. <code>http://my.trilium.org/custom/notes?noteId=123</code>),
<p>Often you also need query params (as in e.g. <code>http://your-trilium-server/custom/notes?noteId=123</code>),
you can get those with standard express <code>req.query.noteId</code>.</p>

View File

@ -0,0 +1,35 @@
<p>Nightly releases are versions built every day, containing the latest improvements
and bugfixes, directly from the main development branch. These versions
are generally useful in preparation for a release, to ensure that there
are no significant bugs that need to be addressed first, or they can be
used to confirm whether a particular bug is fixed or feature is well implemented.</p>
<h2>Regarding the stability</h2>
<p>Despite being on a development branch, generally the main branch is pretty
stable since PRs are tested before they are merged. If you notice any issues,
feel free to report them either via a ticket or via the Matrix.</p>
<h2>Downloading the nightly release manually</h2>
<p>Go to <a href="https://github.com/TriliumNext/Trilium/releases/tag/nightly">github.com/TriliumNext/Trilium/releases/tag/nightly</a> and
look for the artifacts starting with <code>TriliumNotes-main</code>. Choose
the appropriate one for your platform (e.g. <code>windows-x64.zip</code>).</p>
<p>Depending on your use case, you can either test the portable version or
even use the installer.</p>
<aside class="admonition note">
<p>If you choose the installable version (e.g. the .exe on Windows), it will
replace your stable installation.</p>
</aside>
<aside class="admonition important">
<p>By default, the nightly uses the same database as the production version.
Generally you could easily downgrade if needed. However, if there are changes
to the database or sync version, it will not be possible to downgrade without
having to restore from a backup.</p>
</aside>
<h2>Automatically download and install the latest nightly</h2>
<p>This is pretty useful if you are a beta tester that wants to periodically
update their version:</p>
<p>On Ubuntu:</p><pre><code class="language-text-x-trilium-auto">#!/usr/bin/env bash
name=TriliumNotes-linux-x64-nightly.deb
rm -f $name*
wget https://github.com/TriliumNext/Trilium/releases/download/nightly/$name
sudo apt-get install ./$name
rm $name</code></pre>

View File

@ -0,0 +1,42 @@
<aside class="admonition warning">
<p>This functionality is still in preview, expect possible issues or even
the feature disappearing completely.
<br>Feel free to <a href="#root/pOsGYCXsbNQG/BgmBlOIl72jZ/_help_wy8So3yZZlH9">report</a> any
issues you might have.</p>
</aside>
<p>The read-only database is an alternative to&nbsp;<a class="reference-link"
href="#root/pOsGYCXsbNQG/tC7s2alapj8V/_help_R9pX4DGra2Vt">Sharing</a>&nbsp;notes.
Although the share functionality works pretty well to publish pages to
the Internet in a wiki, blog-like format it does not offer the full functionality
behind Trilium (such as the advanced&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/wArbEsdSae6g/_help_eIg8jdvaoNNd">Search</a>&nbsp;or
the interactivity behind&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/_help_GTwFsgaA0lCt">Collections</a>&nbsp;or
the various&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/_help_KSZ04uQ2D1St">Note Types</a>).</p>
<p>When the database is in read-only mode, the Trilium application can be
used as normal, but editing is disabled and changes are made in-memory
only.</p>
<h2>What it does</h2>
<ul>
<li data-list-item-id="e97d71c2d01fa683d840e3f8de9e5bea9">All notes are read-only, without the possibility of editing them.</li>
<li
data-list-item-id="e6e82b25093f659cca922614ea8701ca4">Features that would normally alter the database such as the list of recent
notes are disabled.</li>
</ul>
<h2>Limitations</h2>
<ul>
<li data-list-item-id="ec1a648467ed42eee3d6b96f829b6daad">Some features might “slip through” and still end up creating a note, for
example.
<ul>
<li data-list-item-id="ed98de88bf24f574cfda28cf278be7f86">However, the database is still read-only, so all modifications will be
reset if the server is restarted.</li>
<li data-list-item-id="e3f0450830304fa98038a8a5fd9026b06">Whenever this occurs, <code>ERROR: read-only DB ignored</code> will be shown
in the logs.</li>
</ul>
</li>
</ul>
<h2>Setting a database as read-only</h2>
<p>First, make sure the database is initialized (e.g. the first set up is
complete). Then modify the <a href="#root/pOsGYCXsbNQG/tC7s2alapj8V/_help_Gzjqa934BdH4">config.ini</a> by
looking for the <code>[General]</code> section and adding a new <code>readOnly</code> field:</p><pre><code class="language-text-x-trilium-auto">[General]
readOnly=true</code></pre>
<p>If your server is already running, restart it to apply the changes.</p>
<p>Similarly, to disable read-only remove the line or set it to <code>false</code>.</p>

View File

@ -4,12 +4,9 @@
script to enable it.</p>
<p>What it does:</p>
<ul>
<li data-list-item-id="e9c42a70a65d868a17df3413c17b7e6cf">Disables <code>customWidget</code> launcher types in <code>app/widgets/containers/launcher.js</code>.</li>
<li
data-list-item-id="ecd3fb5a4737b444e0c850eaea3877261">Disables the running of <code>mobileStartup</code> or <code>frontendStartup</code> scripts.</li>
<li
data-list-item-id="ea137da7a8e7ea33537b6ccf972a24728">Displays the root note instead of the previously saved session.</li>
<li
data-list-item-id="eae25286d35c9c22f543fddb8475d09aa">Disables the running of <code>backendStartup</code>, <code>hourly</code>, <code>daily</code> scripts
and checks for the hidden subtree.</li>
<li>Disables <code>customWidget</code> launcher types in <code>app/widgets/containers/launcher.js</code>.</li>
<li>Disables the running of <code>mobileStartup</code> or <code>frontendStartup</code> scripts.</li>
<li>Displays the root note instead of the previously saved session.</li>
<li>Disables the running of <code>backendStartup</code>, <code>hourly</code>, <code>daily</code> scripts
and checks for the hidden subtree.</li>
</ul>

View File

@ -21,7 +21,7 @@
<ol>
<li>Set the text to search for in the <em>Search string</em> field.
<ol>
<li>Apart from searching for words ad-literam, there is also the possibility
<li>Apart from searching for words literally, there is also the possibility
to search for attributes or properties of notes.</li>
<li>See the examples below for more information.</li>
</ol>

View File

@ -31,7 +31,7 @@
and you will see a list of all modified notes including the deleted ones.
Notes available for undeletion have a link to do so. This is kind of "trash
can" functionality known from e.g. Windows.</p>
<p>Clicking an undelete will recover the note, it's content and attributes
<p>Clicking an undelete will recover the note, its content and attributes
- note should be just as before being deleted. This action will also undelete
note's children which have been deleted in the same action.</p>
<p>To be able to undelete a note, it is necessary that deleted note's parent

View File

@ -29,7 +29,7 @@
<li><em><strong>Editable</strong></em> changes whether the current note:
<ul>
<li>Enters <a href="#root/_help_CoFPLs3dRlXc">read-only mode</a> automatically if
the note is too big (default behaviour).</li>
the note is too big (default behavior).</li>
<li>Is always in read-only mode (however it can still be edited temporarily).</li>
<li>Is always editable, regardless of its size.</li>
</ul>

View File

@ -1,5 +1,5 @@
<p>Collections are a unique type of notes that don't have a content, but
instead display its child notes in various presentation methods.</p>
<p>Collections are a unique type of note that don't have content, but instead
display their child notes in various presentation methods.</p>
<h2>Main collections</h2>
<table>
<thead>
@ -94,7 +94,7 @@
in the&nbsp;<a class="reference-link" href="#root/_help_BlN9DFI679QC">Ribbon</a>.</p>
<h2>Archived notes</h2>
<p>By default, <a href="#root/_help_MKmLg5x6xkor">archived notes</a> will not be
shown in collections. This behaviour can be changed by going to <em>Collection Properties</em> in
shown in collections. This behavior can be changed by going to <em>Collection Properties</em> in
the&nbsp;<a class="reference-link" href="#root/_help_BlN9DFI679QC">Ribbon</a>&nbsp;and
checking <em>Show archived notes</em>.</p>
<p>Archived notes will be generally indicated by being greyed out as opposed

View File

@ -1,12 +1,12 @@
<p>Data directory contains:</p>
<ul>
<li><code>document.db</code> - <a href="#root/_help_wX4HbRucYSDD">database</a>
<li data-list-item-id="e02c8e6918e645272972d94bf51bd34e1"><code>document.db</code> - <a href="#root/_help_wX4HbRucYSDD">database</a>
</li>
<li><code>config.ini</code> - instance level settings like port on which the
<li data-list-item-id="ee3b386f752a4d2de88a1362ff4bc447a"><code>config.ini</code> - instance level settings like port on which the
Trilium application runs</li>
<li><code>backup</code> - contains automatically <a href="#root/_help_ODY7qQn5m2FT">backup</a> of
<li data-list-item-id="e9acb98fe27a493914c7f61574b277d59"><code>backup</code> - contains automatically <a href="#root/_help_ODY7qQn5m2FT">backup</a> of
documents</li>
<li><code>log</code> - contains application log files</li>
<li data-list-item-id="e9f5de6d3c7f8d79710eb8c8e2ccac979"><code>log</code> - contains application log files</li>
</ul>
<h2>Location of the data directory</h2>
<p>Easy way how to find out which data directory Trilium uses is to look
@ -18,30 +18,42 @@
<p>Data directory is normally named <code>trilium-data</code> and it is stored
in:</p>
<ul>
<li><code>/home/[user]/.local/share</code> for Linux</li>
<li><code>C:\Users\[user]\AppData\Roaming</code> for Windows Vista and up</li>
<li><code>/Users/[user]/Library/Application Support</code> for Mac OS</li>
<li>user's home is a fallback if some of the paths above don't exist</li>
<li>user's home is also a default setup for [[docker|Docker server installation]]</li>
<li data-list-item-id="e52706d339ce2332e73c5cfdc51edf81d"><code>/home/[user]/.local/share</code> for Linux</li>
<li data-list-item-id="e5709c767479c028d2ef60d17124ea924"><code>C:\Users\[user]\AppData\Roaming</code> for Windows Vista and up</li>
<li
data-list-item-id="eae590672aea8b24e625f372fb22d1f1b"><code>/Users/[user]/Library/Application Support</code> for Mac OS</li>
<li
data-list-item-id="e150ab7fc3c436d7cbac192acf3fe433f">user's home is a fallback if some of the paths above don't exist</li>
<li
data-list-item-id="e5f476ac39d8fa453e19b8399073a79d7">user's home is also a default setup for [[docker|Docker server installation]]</li>
</ul>
<p>If you want to back up your Trilium data, just backup this single directory
- it contains everything you need.</p>
<h3>Changing the location of data directory</h3>
<p>If you want to use some other location for the data directory than the
default one, you may change it via TRILIUM_DATA_DIR environment variable
to some other location:</p>
default one, you may change it via <code>TRILIUM_DATA_DIR</code> environment
variable to some other location:</p>
<h3>Windows</h3>
<ol>
<li data-list-item-id="e8597645795a4e5b079b8e2e4a772a7d7">Press the Windows key on your keyboard.</li>
<li data-list-item-id="ee32856df1db7c2d206baa480a672eeac">Search and select “Edit the system variables”.</li>
<li data-list-item-id="e08c7cdc0659b414465a07c78dfdaeb34">Press the “Environment Variables…” button in the bottom-right of the newly
opened screen.</li>
<li data-list-item-id="e6c7bae2590161a2661b6229c0853021c">On the top section ("User variables for [user]"), press the “New…” button.</li>
<li
data-list-item-id="effac7dc3976f17d309958dd3374cfa8d">In the <em>Variable name</em> field insert <code>TRILIUM_DATA_DIR</code>.</li>
<li
data-list-item-id="e3b74fdd03536563bb461e11111fae5ff">Press the <em>Browse Directory…</em> button and select the new directory
where to store the database.</li>
<li data-list-item-id="ed6df567f0fa772a14610b3904a0cb635">Close all the windows by pressing the <em>OK</em> button for each of them.</li>
</ol>
<h4>Linux</h4><pre><code class="language-text-x-trilium-auto">export TRILIUM_DATA_DIR=/home/myuser/data/my-trilium-data</code></pre>
<h4>Mac OS X</h4>
<p>You need to create a <code>.plist</code> file under <code>~/Library/LaunchAgents</code> to
load it properly each login.</p>
<p>To load it manually, you need to use <code>launchctl setenv TRILIUM_DATA_DIR &lt;yourpath&gt;</code>
</p>
<p>Here is a pre-defined template, where you just need to add your path to:</p><pre><code class="language-text-x-trilium-auto">
Label
<p>Here is a pre-defined template, where you just need to add your path to:</p><pre><code class="language-text-x-trilium-auto"> Label
set.trilium.env
RunAtLoad
@ -50,76 +62,75 @@
launchctl
setenv
TRILIUM_DATA_DIR
/Users/YourUserName/Library/Application Support/trilium-data
</code></pre>
/Users/YourUserName/Library/Application Support/trilium-data </code></pre>
<h3>Create a script to run with specific data directory</h3>
<p>An alternative to globally setting environment variable is to run only
the Trilium Notes with this environment variable. This then allows for
different setup styles like two <a href="#root/_help_wX4HbRucYSDD">database</a> instances
or "portable" installation.</p>
<p>To do this in unix based systems simply run trilium like this:</p><pre><code class="language-text-x-trilium-auto">TRILIUM_DATA_DIR=/home/myuser/data/my-trilium-data trilium</code></pre>
<p>To do this in Unix-based systems simply run <code>trilium</code> like this:</p><pre><code class="language-text-x-trilium-auto">TRILIUM_DATA_DIR=/home/myuser/data/my-trilium-data trilium</code></pre>
<p>You can then save the above command as a shell script on your path for
convenience.</p>
<h2>Fine-grained directory/path location</h2>
<p>Apart from the data directory, some of the subdirectories of it can be
moved elsewhere by changing an environment variable:</p>
<table>
<thead>
<tr>
<th>Environment variable</th>
<th>Default value</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>TRILIUM_DOCUMENT_PATH</code>
</td>
<td><code>${TRILIUM_DATA_DIR}/document.db</code>
</td>
<td>Path to the&nbsp;<a class="reference-link" href="#root/_help_wX4HbRucYSDD">Database</a>&nbsp;(storing
all notes and metadata).</td>
</tr>
<tr>
<td><code>TRILIUM_BACKUP_DIR</code>
</td>
<td><code>${TRILIUM_DATA_DIR}/backup</code>
</td>
<td>Directory where automated&nbsp;<a class="reference-link" href="#root/_help_ODY7qQn5m2FT">Backup</a>&nbsp;databases
are stored.</td>
</tr>
<tr>
<td><code>TRILIUM_LOG_DIR</code>
</td>
<td><code>${TRILIUM_DATA_DIR}/log</code>
</td>
<td>Directory where daily&nbsp;<a class="reference-link" href="#root/_help_bnyigUA2UK7s">Backend (server) logs</a>&nbsp;are
stored.</td>
</tr>
<tr>
<td><code>TRILIUM_TMP_DIR</code>
</td>
<td><code>${TRILIUM_DATA_DIR}/tmp</code>
</td>
<td>Directory where temporary files are stored (for example when opening in
an external app).</td>
</tr>
<tr>
<td><code>TRILIUM_ANONYMIZED_DB_DIR</code>
</td>
<td><code>${TRILIUM_DATA_DIR}/anonymized-db</code>
</td>
<td>Directory where a&nbsp;<a class="reference-link" href="#root/_help_x59R8J8KV5Bp">Anonymized Database</a>&nbsp;is
stored.</td>
</tr>
<tr>
<td><code>TRILIUM_CONFIG_INI_PATH</code>
</td>
<td><code>${TRILIUM_DATA_DIR}/config.ini</code>
</td>
<td>Path to&nbsp;<a class="reference-link" href="#root/_help_Gzjqa934BdH4">Configuration (config.ini or environment variables)</a>&nbsp;file.</td>
</tr>
</tbody>
</table>
<figure class="table">
<table>
<thead>
<tr>
<th>Environment variable</th>
<th>Default value</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>TRILIUM_DOCUMENT_PATH</code>
</td>
<td><code>${TRILIUM_DATA_DIR}/document.db</code>
</td>
<td>Path to the&nbsp;<a class="reference-link" href="#root/_help_wX4HbRucYSDD">Database</a>&nbsp;(storing
all notes and metadata).</td>
</tr>
<tr>
<td><code>TRILIUM_BACKUP_DIR</code>
</td>
<td><code>${TRILIUM_DATA_DIR}/backup</code>
</td>
<td>Directory where automated&nbsp;<a class="reference-link" href="#root/_help_ODY7qQn5m2FT">Backup</a>&nbsp;databases
are stored.</td>
</tr>
<tr>
<td><code>TRILIUM_LOG_DIR</code>
</td>
<td><code>${TRILIUM_DATA_DIR}/log</code>
</td>
<td>Directory where daily&nbsp;<a class="reference-link" href="#root/_help_bnyigUA2UK7s">Backend (server) logs</a>&nbsp;are
stored.</td>
</tr>
<tr>
<td><code>TRILIUM_TMP_DIR</code>
</td>
<td><code>${TRILIUM_DATA_DIR}/tmp</code>
</td>
<td>Directory where temporary files are stored (for example when opening in
an external app).</td>
</tr>
<tr>
<td><code>TRILIUM_ANONYMIZED_DB_DIR</code>
</td>
<td><code>${TRILIUM_DATA_DIR}/anonymized-db</code>
</td>
<td>Directory where a&nbsp;<a class="reference-link" href="#root/_help_x59R8J8KV5Bp">Anonymized Database</a>&nbsp;is
stored.</td>
</tr>
<tr>
<td><code>TRILIUM_CONFIG_INI_PATH</code>
</td>
<td><code>${TRILIUM_DATA_DIR}/config.ini</code>
</td>
<td>Path to&nbsp;<a class="reference-link" href="#root/_help_Gzjqa934BdH4">Configuration (config.ini or environment variables)</a>&nbsp;file.</td>
</tr>
</tbody>
</table>
</figure>

View File

@ -27,6 +27,6 @@
any startup scripts that might cause the application to crash.</li>
</ul>
<h2>Synchronization</h2>
<p>For Trilium desktp users who wish to synchronize their data with a server
<p>For Trilium desktop users who wish to synchronize their data with a server
instance, refer to the&nbsp;<a class="reference-link" href="#root/_help_cbkrhQjrkKrh">Synchronization</a>&nbsp;guide
for detailed instructions.</p>

View File

@ -40,7 +40,7 @@
<h3>Disabling / Modifying the Upload Limit</h3>
<p>If you're running into the 250MB limit imposed on the server by default,
and you'd like to increase the upload limit, you can set the <code>TRILIUM_NO_UPLOAD_LIMIT</code> environment
variable to <code>true</code> disable it completely:</p><pre><code class="language-text-x-trilium-auto">export TRILIUM_NO_UPLOAD_LIMIT=true </code></pre>
variable to <code>true</code> to disable it completely:</p><pre><code class="language-text-x-trilium-auto">export TRILIUM_NO_UPLOAD_LIMIT=true </code></pre>
<p>Or, if you'd simply like to <em>increase</em> the upload limit size to something
beyond 250MB, you can set the <code>MAX_ALLOWED_FILE_SIZE_MB</code> environment
variable to something larger than the integer <code>250</code> (e.g. <code>450</code> in

View File

@ -1,5 +1,5 @@
<p>One core features of Trilium is that it supports multiple types of notes,
depending on the need.</p>
<p>One of the core features of Trilium is that it supports multiple types
of notes, depending on the need.</p>
<h2>Creating a new note with a different type via the note tree</h2>
<p>The default note type in Trilium (e.g. when creating a new note) is&nbsp;
<a

View File

@ -3,7 +3,7 @@
it. Special case is JavaScript code notes which can also be executed inside
Trilium which can in conjunction with&nbsp;<a class="reference-link" href="#root/_help_GLks18SNjxmC">Script API</a>&nbsp;provide
extra functionality.</p>
<h2>Scripting</h2>
<h2>Architecture Overview</h2>
<p>To go further I must explain basic architecture of Trilium - in its essence
it is a classic web application - it has these two main components:</p>
<ul>
@ -14,8 +14,8 @@
</ul>
<p>So we have frontend and backend, each with their own set of responsibilities,
but their common feature is that they both run JavaScript code. Add to
this the fact, that we're able to create JavaScript [[code notes]] and
we're onto something.</p>
this the fact, that we're able to create JavaScript <a class="reference-link"
href="#root/_help_6f9hih2hXXZk">code notes</a> and we're onto something.</p>
<h2>Use cases</h2>
<ul>
<li><a class="reference-link" href="#root/_help_TjLYAo3JMO8X">"New Task" launcher button</a>

View File

@ -0,0 +1,7 @@
<p>Older versions of Trilium Notes allowed the use of Common.js module imports
inside backend scripts, such as:</p><pre><code class="language-text-x-trilium-auto">const isBetween = require('dayjs/plugin/isBetween')
api.dayjs.extend(isBetween)</code></pre>
<p>For newer versions, Node.js imports are <strong>not officially supported anymore</strong>,
since we've added a bundler which makes it more difficult to reuse dependencies.</p>
<p>Theoretically it's still possible to use imports by manually setting up
a <code>node_modules</code> in the server directory via <code>npm</code> or <code>pnpm</code>.</p>

View File

@ -0,0 +1,9 @@
<p>In <code>doRender()</code>:</p><pre><code class="language-text-x-trilium-auto">this.cssBlock(`#my-widget {
position: absolute;
bottom: 40px;
left: 60px;
z-index: 1;
}`)</code></pre>
<hr>
<p>Reference: <a href="https://trilium.rocks/X7pxYpiu0lgU">https://trilium.rocks/X7pxYpiu0lgU</a>
</p>

View File

@ -0,0 +1,30 @@
<ul>
<li><code>doRender</code> must not be overridden, instead <code>doRenderBody()</code> has
to be overridden.</li>
<li><code>parentWidget()</code> must be set to <code>“rightPane”</code>.</li>
<li><code>widgetTitle()</code> getter can optionally be overriden, otherwise
the widget will be displayed as “Untitled widget”.</li>
</ul><pre><code class="language-text-x-trilium-auto">const template = `&lt;div&gt;Hi&lt;/div&gt;`;
class ToDoListWidget extends api.RightPanelWidget {
get widgetTitle() {
return "Title goes here";
}
get parentWidget() { return "right-pane" }
doRenderBody() {
this.$body.empty().append($(template));
}
async refreshWithNote(note) {
this.toggleInt(false);
this.triggerCommand("reEvaluateRightPaneVisibility");
this.toggleInt(true);
this.triggerCommand("reEvaluateRightPaneVisibility");
}
}
module.exports = new ToDoListWidget();</code></pre>
<p>The implementation is in <code>src/public/app/widgets/right_panel_widget.js</code>.</p>

View File

@ -87,5 +87,17 @@ module.exports = new MyWidget();</code></pre>
}
module.exports = new MyWidget();</code></pre>
<p>Reload the application one last time. When you click the button, a "Hello
World!" message should appear, confirming that your widget is fully functional.</p>
<p><code>parentWidget()</code> can be given the following values:</p>
<ul>
<li><code>left-pane</code> - This renders the widget on the left side of the
screen where the note tree lives.</li>
<li><code>center-pane</code> - This renders the widget in the center of the
layout in the same location that notes and splits appear.</li>
<li><code>note-detail-pane</code> - This renders the widget <em>with</em> the
note in the center pane. This means it can appear multiple times with splits.</li>
<li><code>right-pane</code> - This renders the widget to the right of any opened
notes.</li>
</ul>
<p><a href="#root/_help_s8alTXmpFR61">Reload</a> the application one last time.
When you click the button, a "Hello World!" message should appear, confirming
that your widget is fully functional.</p>

View File

@ -1,4 +1,5 @@
<p>As Trilium is currently in beta, encountering bugs is to be expected.</p>
<p>While Trilium is actively maintained and stable, encountering bugs is
possible.</p>
<h2>General Quick Fix</h2>
<p>The first step in troubleshooting is often a restart.</p>
<p>If you experience an UI issue, the frontend may have entered an inconsistent
@ -15,7 +16,7 @@
variable to reset the open tabs to a single specified note ID (e.g., <code>root</code>).
In Linux, you can set it as follows:</p><pre><code class="language-text-x-trilium-auto">TRILIUM_START_NOTE_ID=root ./trilium</code></pre>
<h2>Broken Script Prevents Application Startup</h2>
<p>If a custom script causes Triliumto crash, and it is set as a startup
<p>If a custom script causes Trilium to crash, and it is set as a startup
script or in an active <a href="#root/_help_MgibgPcfeuGz">custom widget</a>, start
Triliumin "safe mode" to prevent any custom scripts from executing:</p><pre><code class="language-text-x-trilium-auto">TRILIUM_SAFE_MODE=true ./trilium</code></pre>
<p>Depending on your Trilium distribution, you may have pre-made scripts

View File

@ -420,7 +420,7 @@
"end-time": "Ora di fine",
"geolocation": "Geolocalizzazione",
"built-in-templates": "Modelli integrati",
"board": "Tavola",
"board": "Kanban Board",
"status": "Stato",
"board_note_first": "Prima nota",
"board_note_second": "Seconda nota",

View File

@ -417,7 +417,7 @@
"end-time": "結束時間",
"geolocation": "地理位置",
"built-in-templates": "內建模版",
"board": "儀表板",
"board": "板",
"status": "狀態",
"board_note_first": "第一個筆記",
"board_note_second": "第二個筆記",

View File

@ -270,6 +270,38 @@ function setPrefix(req: Request) {
branch.save();
}
function setPrefixBatch(req: Request) {
const { branchIds, prefix } = req.body;
if (!Array.isArray(branchIds)) {
throw new ValidationError("branchIds must be an array");
}
// Validate that prefix is a string or null/undefined to prevent prototype pollution
if (prefix !== null && prefix !== undefined && typeof prefix !== 'string') {
throw new ValidationError("prefix must be a string or null");
}
const normalizedPrefix = utils.isEmptyOrWhitespace(prefix) ? null : prefix;
let updatedCount = 0;
for (const branchId of branchIds) {
const branch = becca.getBranch(branchId);
if (branch) {
branch.prefix = normalizedPrefix;
branch.save();
updatedCount++;
} else {
log.info(`Branch ${branchId} not found, skipping prefix update`);
}
}
return {
success: true,
count: updatedCount
};
}
export default {
moveBranchToParent,
moveBranchBeforeNote,
@ -277,5 +309,6 @@ export default {
setExpanded,
setExpandedForSubtree,
deleteBranch,
setPrefix
setPrefix,
setPrefixBatch
};

View File

@ -154,6 +154,7 @@ function register(app: express.Application) {
apiRoute(PUT, "/api/branches/:branchId/expanded-subtree/:expanded", branchesApiRoute.setExpandedForSubtree);
apiRoute(DEL, "/api/branches/:branchId", branchesApiRoute.deleteBranch);
apiRoute(PUT, "/api/branches/:branchId/set-prefix", branchesApiRoute.setPrefix);
apiRoute(PUT, "/api/branches/set-prefix-batch", branchesApiRoute.setPrefixBatch);
apiRoute(GET, "/api/notes/:noteId/attachments", attachmentsApiRoute.getAttachments);
apiRoute(PST, "/api/notes/:noteId/attachments", attachmentsApiRoute.saveAttachment);

View File

@ -158,13 +158,11 @@ function startHttpServer(app: Express) {
// Not all situations require showing an error dialog. When Trilium is already open,
// clicking the shortcut, the software icon, or the taskbar icon, or when creating a new window,
// should simply focus on the existing window or open a new one, without displaying an error message.
if ("code" in error && error.code == "EADDRINUSE") {
if (process.argv.includes("--new-window") || !app.requestSingleInstanceLock()) {
console.error(message);
process.exit(1);
}
if ("code" in error && error.code === "EADDRINUSE" && (process.argv.includes("--new-window") || !app.requestSingleInstanceLock())) {
console.error(message);
} else {
dialog.showErrorBox("Error while initializing the server", message);
}
dialog.showErrorBox("Error while initializing the server", message);
process.exit(1);
});
} else {

View File

@ -14,11 +14,11 @@
"preact": "10.27.2",
"preact-iso": "2.11.0",
"preact-render-to-string": "6.6.3",
"react-i18next": "16.2.3"
"react-i18next": "16.2.4"
},
"devDependencies": {
"@preact/preset-vite": "2.10.2",
"eslint": "9.39.0",
"eslint": "9.39.1",
"eslint-config-preact": "2.0.0",
"typescript": "5.9.3",
"user-agent-data-types": "0.4.2",

View File

@ -70,7 +70,7 @@
"calendar_description": "Organizza i tuoi eventi personali o professionali utilizzando un calendario, con supporto per eventi di un giorno intero o di più giorni. Visualizza i tuoi eventi a colpo d'occhio con le viste settimanale, mensile e annuale. Interazione semplice per aggiungere o trascinare eventi.",
"table_title": "Tabella",
"table_description": "Visualizza e modifica le informazioni relative alle note in una struttura tabellare, con vari tipi di colonne quali testo, numeri, caselle di controllo, data e ora, collegamenti e colori, oltre al supporto per le relazioni. Facoltativamente, è possibile visualizzare le note all'interno di una gerarchia ad albero all'interno della tabella.",
"board_title": "Board",
"board_title": "Kanban Board",
"board_description": "Organizza le tue attività o lo stato dei tuoi progetti in una lavagna Kanban con un modo semplice per creare nuovi elementi e colonne e modificare facilmente il loro stato trascinandoli sulla lavagna.",
"geomap_title": "Geomappa",
"geomap_description": "Pianifica le tue vacanze o segna i tuoi punti di interesse direttamente su una mappa geografica utilizzando indicatori personalizzabili. Visualizza le tracce GPX registrate per seguire gli itinerari.",

View File

@ -70,7 +70,7 @@
"calendar_description": "使用行事曆規劃個人或專業活動,支援全天及多日活動。透過週、月、年檢視模式,一覽所有活動。通過簡單互動即可新增或拖曳活動。",
"table_title": "表格",
"table_description": "以表格結構顯示並編輯筆記資訊,支援多種欄位類型,包括文字、數字、核取方塊、日期與時間、連結及顏色,並支援關聯功能。可選擇性地在表格內以樹狀層級結構顯示筆記內容。",
"board_title": "儀表板",
"board_title": "板",
"board_description": "將您的任務或專案狀態整理成看板,輕鬆建立新項目與欄位,並透過在看板上拖曳即可簡單變更狀態。",
"geomap_title": "地理地圖",
"geomap_description": "使用可自訂的標記直接在地圖上規劃您的假期行程或標記感興趣的地點。顯示已記錄的GPX軌跡以便追蹤行程路線。",

View File

@ -10,7 +10,8 @@
"jsxImportSource": "preact",
"skipLibCheck": true,
"types": [
"vite/client"
"vite/client",
"vitest/config"
],
"paths": {
"react": ["../../node_modules/preact/compat/"],

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,15 @@
# Developer Guide
This documentation is intended for developers planning to implement new features or maintain the Trilium Notes application, as it describes the architecture of the application.
For the user-facing documentation, including how to write scripts and the various APIs, consult the [user guide](https://docs.triliumnotes.org/user-guide/) instead.
For the user-facing documentation, including how to write scripts and the various APIs, consult the [user guide](https://docs.triliumnotes.org/user-guide/) instead.
### Quick links
* <a class="reference-link" href="Developer%20Guide/Environment%20Setup.md">Environment Setup</a>
* <a class="reference-link" href="Developer%20Guide/Project%20Structure.md">Project Structure</a>
### External links
* The [Trilium Notes website](https://triliumnotes.org/), for a quick presentation of the application.
* [User Guide](https://docs.triliumnotes.org/user-guide/), to understand the concepts of the application itself.
* [GitHub Repository (TriliumNext/Trilium)](https://github.com/TriliumNext/Trilium/)

View File

@ -0,0 +1,202 @@
# Architecture
Trilium Notes is a hierarchical note-taking application built as a TypeScript monorepo. It supports multiple deployment modes (desktop, server, mobile web) and features advanced capabilities including synchronization, scripting, encryption, and rich content editing.
### Key Characteristics
* **Monorepo Architecture**: Uses pnpm workspaces for dependency management
* **Multi-Platform**: Desktop (Electron), Server (Node.js/Express), and Mobile Web
* **TypeScript-First**: Strong typing throughout the codebase
* **Plugin-Based**: Extensible architecture for note types and UI components
* **Offline-First**: Full functionality without network connectivity
* **Synchronization-Ready**: Built-in sync protocol for multi-device usage
### Technology Stack
* **Runtime**: Node.js (backend), Browser/Electron (frontend)
* **Language**: TypeScript, JavaScript
* **Database**: SQLite (better-sqlite3)
* **Build Tools**:
* Client: Vite,
* Server: ESBuild (bundling)
* Package manager: pnpm
* **UI Framework**: Custom widget-based system (vanilla HTML, CSS & JavaScript + jQuery), in the process of converting to React/Preact.
* **Rich Text**: CKEditor 5 (customized)
* **Code Editing**: CodeMirror 6
* **Desktop**: Electron
* **Server**: Express.js
## Main architecture
Trilium follows a **client-server architecture** even in desktop mode, where Electron runs both the backend server and frontend client within the same process.
```
graph TB
subgraph Frontend
Widgets[Widgets<br/>System]
Froca[Froca<br/>Cache]
UIServices[UI<br/>Services]
end
subgraph Backend["Backend Server"]
Express[Express<br/>Routes]
Becca[Becca<br/>Cache]
ScriptEngine[Script<br/>Engine]
Database[(SQLite<br/>Database)]
end
Widgets -.-> API[WebSocket & REST API]
Froca -.-> API
UIServices -.-> API
API -.-> Express
API -.-> Becca
API -.-> ScriptEngine
Becca --> Database
Express --> Database
ScriptEngine --> Database
```
### Deployment Modes
1. **Desktop Application**
* Electron wrapper running both frontend and backend
* Local SQLite database
* Full offline functionality
* Cross-platform (Windows, macOS, Linux)
2. **Server Installation**
* Node.js server exposing web interface
* Multi-user capable
* Can sync with desktop clients
* Docker deployment supported
3. **Mobile Web**
* Optimized responsive interface
* Accessed via browser
* Requires server installation
## Monorepo Structure
Trilium uses **pnpm workspaces** to manage its monorepo structure, with apps and packages clearly separated.
```
trilium/
├── apps/ # Runnable applications
│ ├── client/ # Frontend application (shared by server & desktop)
│ ├── server/ # Node.js server with web interface
│ ├── desktop/ # Electron desktop application
│ ├── web-clipper/ # Browser extension for web content capture
│ ├── db-compare/ # Database comparison tool
│ ├── dump-db/ # Database export tool
│ ├── edit-docs/ # Documentation editing tool
│ ├── build-docs/ # Documentation build tool
│ └── website/ # Marketing website
├── packages/ # Shared libraries
│ ├── commons/ # Shared interfaces and utilities
│ ├── ckeditor5/ # Custom rich text editor
│ ├── codemirror/ # Code editor customizations
│ ├── highlightjs/ # Syntax highlighting
│ ├── ckeditor5-admonition/ # CKEditor plugin: admonitions
│ ├── ckeditor5-footnotes/ # CKEditor plugin: footnotes
│ ├── ckeditor5-keyboard-marker/# CKEditor plugin: keyboard shortcuts
│ ├── ckeditor5-math/ # CKEditor plugin: math equations
│ ├── ckeditor5-mermaid/ # CKEditor plugin: diagrams
│ ├── express-partial-content/ # HTTP partial content middleware
│ ├── share-theme/ # Shared note theme
│ ├── splitjs/ # Split pane library
│ └── turndown-plugin-gfm/ # Markdown conversion
├── docs/ # Documentation
├── scripts/ # Build and utility scripts
└── patches/ # Package patches (via pnpm)
```
### Package Dependencies
The monorepo uses workspace protocol (`workspace:*`) for internal dependencies:
```
desktop → client → commons
server → client → commons
client → ckeditor5, codemirror, highlightjs
ckeditor5 → ckeditor5-* plugins
```
## Security summary
### Encryption System
**Per-Note Encryption:**
* Notes can be individually protected
* AES-128-CBC encryption for encrypted notes.
* Separate protected session management
**Protected Session:**
* Time-limited access to protected notes
* Automatic timeout
* Re-authentication required
* Frontend: `protected_session.ts`
* Backend: `protected_session.ts`
### Authentication
**Password Auth:**
* PBKDF2 key derivation
* Salt per installation
* Hash verification
**OpenID Connect:**
* External identity provider support
* OAuth 2.0 flow
* Configurable providers
**TOTP (2FA):**
* Time-based one-time passwords
* QR code setup
* Backup codes
### Authorization
**Single-User Model:**
* Desktop: single user (owner)
* Server: single user per installation
**Share Notes:**
* Public access without authentication
* Separate Shaca cache
* Read-only access
### CSRF Protection
**CSRF Tokens:**
* Required for state-changing operations
* Token in header or cookie
* Validation middleware
### Input Sanitization
**XSS Prevention:**
* DOMPurify for HTML sanitization
* CKEditor content filtering
* CSP headers
**SQL Injection:**
* Parameterized queries only
* Better-sqlite3 prepared statements
* No string concatenation in SQL
### Dependency Security
**Vulnerability Scanning:**
* Renovate bot for updates
* npm audit integration
* Override vulnerable sub-dependencies

View File

@ -0,0 +1,72 @@
# APIs
### Internal API
**REST Endpoints** (`/api/*`)
Used by the frontend for all operations:
**Note Operations:**
* `GET /api/notes/:noteId` - Get note
* `POST /api/notes/:noteId/content` - Update content
* `PUT /api/notes/:noteId` - Update metadata
* `DELETE /api/notes/:noteId` - Delete note
**Tree Operations:**
* `GET /api/tree` - Get note tree
* `POST /api/branches` - Create branch
* `PUT /api/branches/:branchId` - Update branch
* `DELETE /api/branches/:branchId` - Delete branch
**Search:**
* `GET /api/search?query=...` - Search notes
* `GET /api/search-note/:noteId` - Execute search note
### ETAPI (External API)
Located at: `apps/server/src/etapi/`
**Purpose:** Third-party integrations and automation
**Authentication:** Token-based (ETAPI tokens)
**OpenAPI Spec:** Auto-generated
**Key Endpoints:**
* `/etapi/notes` - Note CRUD
* `/etapi/branches` - Branch management
* `/etapi/attributes` - Attribute operations
* `/etapi/attachments` - Attachment handling
**Example:**
```
curl -H "Authorization: YOUR_TOKEN" \
https://trilium.example.com/etapi/notes/noteId
```
### WebSocket API
Located at: `apps/server/src/services/ws.ts`
**Purpose:** Real-time updates and synchronization
**Protocol:** WebSocket (Socket.IO-like custom protocol)
**Message Types:**
* `sync` - Synchronization request
* `entity-change` - Entity update notification
* `refresh-tree` - Tree structure changed
* `open-note` - Open note in UI
**Client Subscribe:**
```typescript
ws.subscribe('entity-change', (data) => {
froca.processEntityChange(data)
})
```

View File

@ -0,0 +1,62 @@
# Arhitecture Decision Records
## 🚀 Future milestones
* [Mobile](https://github.com/TriliumNext/Trilium/issues/7447)
* [Multi-user](https://github.com/TriliumNext/Trilium/issues/4956)
## Aug 2025 - present: Port the client to React
- [x] [Type widgets](https://github.com/TriliumNext/Trilium/pull/7044)
- [x] [Collections](https://github.com/TriliumNext/Trilium/pull/6837)
- [x] [Various widgets](https://github.com/TriliumNext/Trilium/pull/6830)
- [x] [Floating buttons](https://github.com/TriliumNext/Trilium/pull/6811)
- [x] [Settings](https://github.com/TriliumNext/Trilium/pull/6660)
## Aug 2025 - Move away from NX
We took the decision of moving away from the NX monorepo tool, due to:
* Various issues with the cache, especially after an update of the NX dependencies which required periodical `nx reset` to get rid of.
* Various issues with memory and CPU consumption along the way, due to the NX daemon (including it remaining as a background process after closing the IDE).
* On Windows, almost always there was a freeze on every second build.
* Various hacks that were needed to achieve what we needed (especially for artifacts since NX would not copy assets if they were in `.gitignore` for some arbitrary reason and requiring a patch that made it difficult to maintain across updates).
As a result, we decided to switch to… nothing. Why?
* `pnpm` (which we were already using) covers the basic needs of a monorepo via workspaces on its own.
* Our client-side solution, Vite already supports navigating through projects without requiring built artifacts. This makes the build process slightly faster (especially cold starts) at a slighter bigger RAM consumption.
* ESBuild, on the server-side, also seems happy to go across projects without an issue.
Apart from this:
* In dev mode, the server now runs directly using `tsx` and not built and then run. This means that it'll run much faster.
* We're back to an architecture where the `server` and the `desktop` host their own Vite instance as a middleware. What this means that there is no `client:dev` and no separate port to handle.
* This makes it possible to easily test on mobile in dev mode, since there's a single port to access.
* The downside is that the initial start up time is longer while Vite is spinning up. Nevertheless, it's still slightly faster than it used to be anyway.
* No more asset copying, which should also improve performance.
* No more messing around with the native dependency of `better-sqlite3` that caused those dreaded mismatches when running between server and desktop. We have (hopefully) found a permanent solution for it that involves no user input.
* A decent solution was put in place to allow easier development on NixOS for the desktop application.
* The desktop version has also gained back the ability to automatically refresh the client when a change is made, including live changes for React components.
Migration steps, as a developer:
1. In VS Code, uninstall the NX Console unless you plan to use it for other projects.
2. Remove `.nx` at project level.
3. It's ideal to clean up all your `node_modules` in the project (do note that it's not just the top-level one, but also in `apps/client`, `apps/server`, `apps/desktop`, etc.).
4. Run a `pnpm i` to set up the new dependencies and the installation
5. Instead of `nx run server:serve`, now you can simply run `pnpm dev` while in `apps/server`, or `pnpm server:start` while in the root.
6. When first starting the server, it will take slightly longer than usual to see something on the screen since the dependencies are being rebuilt. Those are later cached so subsequent runs should work better. If you end up with a white screen, simply refresh the page a few times until it shows up correctly.
## Apr 2025: NX-based monorepo
* Goal: Restructure the application from a mix where the client was a subfolder within the server and other dependencies such as <a class="reference-link" href="../Dependencies/CKEditor.md">CKEditor</a> were scattered in various repositories to a monorepo powered by NX.
* [Initial discussion](https://github.com/TriliumNext/Trilium/issues/4941)
* [Relevant PR](https://github.com/TriliumNext/Notes/pull/1773)
## Dec 2024: Front-end conversion to TypeScript
* [Relevant PRs on GitHub](https://github.com/TriliumNext/Notes/pulls?q=is%3Apr+is%3Aclosed+%22Port+frontend+to+TypeScript%22)
## Apr 2024: Back-end conversion to TypeScript
* [Relevant PRs on GitHub](https://github.com/TriliumNext/Notes/pulls?q=is%3Apr+%22convert+backend+to+typescript%22)

View File

@ -0,0 +1,88 @@
# Backend
### Application Entry Point
Location: `apps/server/src/main.ts`
**Startup Sequence:**
1. Load configuration
2. Initialize database
3. Run migrations
4. Load Becca cache
5. Start Express server
6. Initialize WebSocket
7. Start scheduled tasks
### Service Layer
Located at: `apps/server/src/services/`
**Core Services:**
* **Notes Management**
* `notes.ts` - CRUD operations
* `note_contents.ts` - Content handling
* `note_types.ts` - Type-specific logic
* `cloning.ts` - Note cloning/multi-parent
* **Tree Operations**
* `tree.ts` - Tree structure management
* `branches.ts` - Branch operations
* `consistency_checks.ts` - Tree integrity
* **Search**
* `search/search.ts` - Main search engine
* `search/expressions/` - Search expression parsing
* `search/services/` - Search utilities
* **Sync**
* `sync.ts` - Synchronization protocol
* `sync_update.ts` - Update handling
* `sync_mutex.ts` - Concurrency control
* **Scripting**
* `backend_script_api.ts` - Backend script API
* `script_context.ts` - Script execution context
* **Import/Export**
* `import/` - Various import formats
* `export/` - Export to different formats
* `zip.ts` - Archive handling
* **Security**
* `encryption.ts` - Note encryption
* `protected_session.ts` - Session management
* `password.ts` - Password handling
### Route Structure
Located at: `apps/server/src/routes/`
```
routes/
├── index.ts # Route registration
├── api/ # REST API endpoints
│ ├── notes.ts
│ ├── branches.ts
│ ├── attributes.ts
│ ├── search.ts
│ ├── login.ts
│ └── ...
└── custom/ # Special endpoints
├── setup.ts
├── share.ts
└── ...
```
**API Endpoint Pattern:**
```typescript
router.get('/api/notes/:noteId', (req, res) => {
const noteId = req.params.noteId
const note = becca.getNote(noteId)
res.json(note.getPojoWithContent())
})
```
### Middleware
Key middleware components:
* `auth.ts` - Authentication
* `csrf.ts` - CSRF protection
* `request_context.ts` - Request-scoped data
* `error_handling.ts` - Error responses

View File

@ -0,0 +1,40 @@
# Database
Trilium uses **SQLite** (via `better-sqlite3`) as its embedded database engine, providing a reliable, file-based storage system that requires no separate database server. The database stores all notes, their relationships, metadata, and configuration.
Schema location: `apps/server/src/assets/db/schema.sql`
### Data Access Patterns
**Direct SQL:**
```typescript
// apps/server/src/services/sql.ts
sql.getRows("SELECT * FROM notes WHERE type = ?", ['text'])
sql.execute("UPDATE notes SET title = ? WHERE noteId = ?", [title, noteId])
```
**Through Becca:**
```typescript
// Recommended approach - uses cache
const note = becca.getNote('noteId')
note.title = 'New Title'
note.save()
```
**Through Froca (Frontend):**
```typescript
// Read-only access
const note = froca.getNote('noteId')
console.log(note.title)
```
### Database Migrations
* The migration system is in `server/src/migrations/migrations.ts` (actual definitions) and `src/services/migration.ts`.
* Both SQLite and TypeScript migrations are supported.
* Small migrations are contained directly in `src/migrations/migrations.ts`.
* Bigger TypeScript migrations are sequentially numbered (e.g., `XXXX_migration_name.ts`) and dynamically imported by `migrations.ts`.
* Automatic execution on version upgrade.
* Schema version tracked in options table.

View File

@ -6,11 +6,11 @@
| `role` | Text | Non-null | | The role of the attachment: `image` for images that are attached to a note, `file` for uploaded files. |
| `mime` | Text | Non-null | | The MIME type of the attachment (e.g. `image/png`) |
| `title` | Text | Non-null | | The title of the attachment. |
| `isProtected` | Integer | Non-null | 0 | `1` if the entity is [protected](../Protected%20entities.md), `0` otherwise. |
| `isProtected` | Integer | Non-null | 0 | `1` if the entity is [protected](../../../Concepts/Protected%20entities.md), `0` otherwise. |
| `position` | Integer | Non-null | 0 | Not sure where the position is relevant for attachments (saw it with values of 10 and 0). |
| `blobId` | Text | Nullable | `null` | The corresponding `blobId` from the <a class="reference-link" href="blobs.md">blobs</a> table. |
| `dateModified` | Text | Non-null | | Localized modification date (e.g. `2023-11-08 18:43:44.204+0200`) |
| `utcDateModified` | Text | Non-null | | Modification date in UTC format (e.g. `2023-11-08 16:43:44.204Z`) |
| `utcDateScheduledForErasure` | Text | Nullable | `null` | |
| `isDeleted` | Integer | Non-null | | `1` if the entity is [deleted](../Deleted%20notes.md), `0` otherwise. |
| `isDeleted` | Integer | Non-null | | `1` if the entity is [deleted](../../../Concepts/Deleted%20notes.md), `0` otherwise. |
| `deleteId` | Text | Nullable | `null` | |

View File

@ -1,2 +1,2 @@
# attributes
<table><thead><tr><th>Column Name</th><th>Data Type</th><th>Nullity</th><th>Default value</th><th>Description</th></tr></thead><tbody><tr><th><code>attributeId</code></th><td>Text</td><td>Non-null</td><td>&nbsp;</td><td>Unique Id of the attribute (e.g. <code>qhC1vzU4nwSE</code>), can also have a special unique ID for&nbsp;<a class="reference-link" href="#root/r11Bh3uxFGRj">Special notes</a>&nbsp;(e.g. <code>_lbToday_liconClass</code>).</td></tr><tr><th><code>noteId</code></th><td>Text</td><td>Non-null</td><td>&nbsp;</td><td>The ID of the <a href="notes.md">note</a> this atttribute belongs to</td></tr><tr><th><code>type</code></th><td>Text</td><td>Non-null</td><td>&nbsp;</td><td>The type of attribute (<code>label</code> or <code>relation</code>).</td></tr><tr><th><code>name</code></th><td>Text</td><td>Non-null</td><td>&nbsp;</td><td>The name/key of the attribute.</td></tr><tr><th><code>value</code></th><td>Text</td><td>Non-null</td><td><code>""</code></td><td><ul><li>For <code>label</code> attributes, a free-form value of the attribute.</li><li>For <code>relation</code> attributes, the ID of the <a href="notes.md">note</a> the relation is pointing to.</li></ul></td></tr><tr><th><code>position</code></th><td>Integer</td><td>Non-null</td><td>0</td><td>The position of the attribute compared to the other attributes. Some predefined attributes such as <code>originalFileName</code> have a value of 1000.</td></tr><tr><th><code>utcDateModified</code></th><td>Text</td><td>Non-null</td><td>&nbsp;</td><td>Modification date in UTC format (e.g. <code>2023-11-08 16:43:44.204Z</code>)</td></tr><tr><th><code>isDeleted</code></th><td>Integer</td><td>Non-null</td><td>&nbsp;</td><td><code>1</code> if the entity is <a href="../Deleted%20notes.md">deleted</a>, <code>0</code> otherwise.</td></tr><tr><th><code>deleteId</code></th><td>Text</td><td>Nullable</td><td><code>null</code></td><td>&nbsp;</td></tr><tr><th><code>isInheritable</code></th><td>Integer</td><td>Nullable</td><td>0</td><td>&nbsp;</td></tr></tbody></table>
<table><thead><tr><th>Column Name</th><th>Data Type</th><th>Nullity</th><th>Default value</th><th>Description</th></tr></thead><tbody><tr><th><code>attributeId</code></th><td>Text</td><td>Non-null</td><td>&nbsp;</td><td>Unique Id of the attribute (e.g. <code>qhC1vzU4nwSE</code>), can also have a special unique ID for&nbsp;<a class="reference-link" href="#root/r11Bh3uxFGRj">Special notes</a>&nbsp;(e.g. <code>_lbToday_liconClass</code>).</td></tr><tr><th><code>noteId</code></th><td>Text</td><td>Non-null</td><td>&nbsp;</td><td>The ID of the <a href="notes.md">note</a> this atttribute belongs to</td></tr><tr><th><code>type</code></th><td>Text</td><td>Non-null</td><td>&nbsp;</td><td>The type of attribute (<code>label</code> or <code>relation</code>).</td></tr><tr><th><code>name</code></th><td>Text</td><td>Non-null</td><td>&nbsp;</td><td>The name/key of the attribute.</td></tr><tr><th><code>value</code></th><td>Text</td><td>Non-null</td><td><code>""</code></td><td><ul><li>For <code>label</code> attributes, a free-form value of the attribute.</li><li>For <code>relation</code> attributes, the ID of the <a href="notes.md">note</a> the relation is pointing to.</li></ul></td></tr><tr><th><code>position</code></th><td>Integer</td><td>Non-null</td><td>0</td><td>The position of the attribute compared to the other attributes. Some predefined attributes such as <code>originalFileName</code> have a value of 1000.</td></tr><tr><th><code>utcDateModified</code></th><td>Text</td><td>Non-null</td><td>&nbsp;</td><td>Modification date in UTC format (e.g. <code>2023-11-08 16:43:44.204Z</code>)</td></tr><tr><th><code>isDeleted</code></th><td>Integer</td><td>Non-null</td><td>&nbsp;</td><td><code>1</code> if the entity is <a href="../../../Concepts/Deleted%20notes.md">deleted</a>, <code>0</code> otherwise.</td></tr><tr><th><code>deleteId</code></th><td>Text</td><td>Nullable</td><td><code>null</code></td><td>&nbsp;</td></tr><tr><th><code>isInheritable</code></th><td>Integer</td><td>Nullable</td><td>0</td><td>&nbsp;</td></tr></tbody></table>

View File

@ -5,8 +5,8 @@
| `noteId` | Text | Non-null | | The ID of the [note](notes.md). |
| `parentNoteId` | Text | Non-null | | The ID of the parent [note](notes.md) the note belongs to. |
| `notePosition` | Integer | Non-null | | The position of the branch within the same level of hierarchy, the value is usually a multiple of 10. |
| `prefix` | Text | Nullable | | The [branch prefix](../Branch%20prefixes.md) if any, or `NULL` otherwise. |
| `prefix` | Text | Nullable | | The [branch prefix](../../../Concepts/Branch%20prefixes.md) if any, or `NULL` otherwise. |
| `isExpanded` | Integer | Non-null | 0 | Whether the branch should appear expanded (its children shown) to the user. |
| `isDeleted` | Integer | Non-null | 0 | `1` if the entity is [deleted](../Deleted%20notes.md), `0` otherwise. |
| `isDeleted` | Integer | Non-null | 0 | `1` if the entity is [deleted](../../../Concepts/Deleted%20notes.md), `0` otherwise. |
| `deleteId` | Text | Nullable | `null` | |
| `utcDateModified` | Text | Non-null | | Modification date in UTC format (e.g. `2023-11-08 16:43:44.204Z`) |

View File

@ -6,4 +6,4 @@
| `tokenHash` | Text | Non-null | | The token itself. |
| `utcDateCreated` | Text | Non-null | | Creation date in UTC format (e.g. `2023-11-08 16:43:44.204Z`) |
| `utcDateModified` | Text | Non-null | | Modification date in UTC format (e.g. `2023-11-08 16:43:44.204Z`) |
| `isDeleted` | Integer | Non-null | 0 | `1` if the entity is [deleted](../Deleted%20notes.md), `0` otherwise. |
| `isDeleted` | Integer | Non-null | 0 | `1` if the entity is [deleted](../../../Concepts/Deleted%20notes.md), `0` otherwise. |

View File

@ -3,10 +3,10 @@
| --- | --- | --- | --- | --- |
| `noteId` | Text | Non-null | | The unique ID of the note (e.g. `2LJrKqIhr0Pe`). |
| `title` | Text | Non-null | `"note"` | The title of the note, as defined by the user. |
| `isProtected` | Integer | Non-null | 0 | `1` if the entity is [protected](../Protected%20entities.md), `0` otherwise. |
| `isProtected` | Integer | Non-null | 0 | `1` if the entity is [protected](../../../Concepts/Protected%20entities.md), `0` otherwise. |
| `type` | Text | Non-null | `"text"` | The type of note (i.e. `text`, `file`, `code`, `relationMap`, `mermaid`, `canvas`). |
| `mime` | Text | Non-null | `"text/html"` | The MIME type of the note (e.g. `text/html`).. Note that it can be an empty string in some circumstances, but not null. |
| `isDeleted` | Integer | Nullable | 0 | `1` if the entity is [deleted](../Deleted%20notes.md), `0` otherwise. |
| `isDeleted` | Integer | Nullable | 0 | `1` if the entity is [deleted](../../../Concepts/Deleted%20notes.md), `0` otherwise. |
| `deleteId` | Text | Non-null | `null` | |
| `dateCreated` | Text | Non-null | | Localized creation date (e.g. `2023-11-08 18:43:44.204+0200`) |
| `dateModified` | Text | Non-null | | Localized modification date (e.g. `2023-11-08 18:43:44.204+0200`) |

View File

@ -6,7 +6,7 @@
| `type` | Text | Non-null | `""` | The type of note (i.e. `text`, `file`, `code`, `relationMap`, `mermaid`, `canvas`). |
| `mime` | Text | Non-null | `""` | The MIME type of the note (e.g. `text/html`). |
| `title` | Text | Non-null | | The title of the note, as defined by the user. |
| `isProtected` | Integer | Non-null | 0 | `1` if the entity is [protected](../Protected%20entities.md), `0` otherwise. |
| `isProtected` | Integer | Non-null | 0 | `1` if the entity is [protected](../../../Concepts/Protected%20entities.md), `0` otherwise. |
| `blobId` | Text | Nullable | `null` | The corresponding ID from <a class="reference-link" href="blobs.md">blobs</a>. Although it can theoretically be `NULL`, haven't found any such note yet. |
| `utcDateLastEdited` | Text | Non-null | | **Not sure how it differs from modification date.** |
| `utcDateCreated` | Text | Non-null | | Creation date in UTC format (e.g. `2023-11-08 16:43:44.204Z`) |

View File

@ -0,0 +1,61 @@
# Frontend
### Application Entry Point
**Desktop:** `apps/client/src/desktop.ts` **Web:** `apps/client/src/index.ts`
### Service Layer
Located at: `apps/client/src/services/`
Key services:
* `froca.ts` - Frontend cache
* `server.ts` - API communication
* `ws.ts` - WebSocket connection
* `tree_service.ts` - Note tree management
* `note_context.ts` - Active note tracking
* `protected_session.ts` - Encryption key management
* `link.ts` - Note linking and navigation
* `export.ts` - Note export functionality
### UI Components
**Component Locations:**
* `widgets/containers/` - Layout containers
* `widgets/buttons/` - Toolbar buttons
* `widgets/dialogs/` - Modal dialogs
* `widgets/ribbon_widgets/` - Tab widgets
* `widgets/type_widgets/` - Note type editors
### Event System
**Application Events:**
```typescript
// Subscribe to events
appContext.addBeforeUnloadListener(() => {
// Cleanup before page unload
})
// Trigger events
appContext.trigger('noteTreeLoaded')
```
**Note Context Events:**
```typescript
// NoteContextAwareWidget automatically receives:
- noteSwitched()
- noteChanged()
- refresh()
```
### State Management
Trilium uses **custom state management** rather than Redux/MobX:
* `note_context.ts` - Active note and context
* `froca.ts` - Entity cache
* Component local state
* URL parameters for shareable state

View File

@ -1,6 +0,0 @@
# Protected entities
The following entities can be made protected, via their `isProtected` flag:
* <a class="reference-link" href="Database%20structure/attachments.md">attachments</a>
* <a class="reference-link" href="Database%20structure/notes.md">notes</a>
* <a class="reference-link" href="Database%20structure/revisions.md">revisions</a>

View File

@ -0,0 +1,464 @@
# Security
Trilium implements a **defense-in-depth security model** with multiple layers of protection for user data. The security architecture covers authentication, authorization, encryption, input sanitization, and secure communication.
## Security Principles
1. **Data Privacy**: User data is protected at rest and in transit
2. **Encryption**: Per-note encryption for sensitive content
3. **Authentication**: Multiple authentication methods supported
4. **Authorization**: Single-user model with granular protected sessions
5. **Input Validation**: All user input sanitized
6. **Secure Defaults**: Security features enabled by default
7. **Transparency**: Open source allows security audits
## Threat Model
### Threats Considered
1. **Unauthorized Access**
* Physical access to device
* Network eavesdropping
* Stolen credentials
* Session hijacking
2. **Data Exfiltration**
* Malicious scripts
* XSS attacks
* SQL injection
* CSRF attacks
3. **Data Corruption**
* Malicious modifications
* Database tampering
* Sync conflicts
4. **Privacy Leaks**
* Unencrypted backups
* Search indexing
* Temporary files
* Memory dumps
### Out of Scope
* Nation-state attackers
* Zero-day vulnerabilities in dependencies
* Hardware vulnerabilities (Spectre, Meltdown)
* Physical access with unlimited time
* Quantum computing attacks
## Authentication
### Password Authentication
**Implementation:** `apps/server/src/services/password.ts`
### TOTP (Two-Factor Authentication)
**Implementation:** `apps/server/src/routes/api/login.ts`
### OpenID Connect
**Implementation:** `apps/server/src/routes/api/login.ts`
**Supported Providers:**
* Any OpenID Connect compatible provider
* Google, GitHub, Auth0, etc.
**Flow:**
```typescript
// 1. Redirect to provider
GET /api/login/openid
// 2. Provider redirects back with code
GET /api/login/openid/callback?code=...
// 3. Exchange code for tokens
const tokens = await openidClient.callback(redirectUri, req.query)
// 4. Verify ID token
const claims = tokens.claims()
// 5. Create session
req.session.loggedIn = true
```
### Session Management
**Session Storage:** SQLite database (sessions table)
**Session Configuration:**
```typescript
app.use(session({
secret: sessionSecret,
resave: false,
saveUninitialized: false,
rolling: true,
cookie: {
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
httpOnly: true,
secure: isHttps,
sameSite: 'lax'
},
store: new SqliteStore({
db: db,
table: 'sessions'
})
}))
```
**Session Invalidation:**
* Automatic timeout after inactivity
* Manual logout clears session
* Server restart invalidates all sessions (optional)
## Authorization
### Single-User Model
**Desktop:**
* Single user (owner of device)
* No multi-user support
* Full access to all notes
**Server:**
* Single user per installation
* Authentication required for all operations
* No user roles or permissions
### Protected Sessions
**Purpose:** Temporary access to encrypted (protected) notes
**Implementation:** `apps/server/src/services/protected_session.ts`
**Workflow:**
```typescript
// 1. User enters password for protected notes
POST /api/protected-session/enter
Body: { password: "protected-password" }
// 2. Derive encryption key
const protectedDataKey = deriveKey(password)
// 3. Verify password (decrypt known encrypted value)
const decrypted = decrypt(testValue, protectedDataKey)
if (decrypted === expectedValue) {
// 4. Store in memory (not in session)
protectedSessionHolder.setProtectedDataKey(protectedDataKey)
// 5. Set timeout
setTimeout(() => {
protectedSessionHolder.clearProtectedDataKey()
}, timeout)
}
```
**Protected Session Timeout:**
* Default: 10 minutes (configurable)
* Extends on activity
* Cleared on browser close
* Separate from main session
### API Authorization
**Internal API:**
* Requires authenticated session
* CSRF token validation
* Same-origin policy
**ETAPI (External API):**
* Token-based authentication
* No session required
* Rate limiting
## Encryption
### Note Encryption
**Encryption Algorithm:** AES-256-CBC
**Key Hierarchy:**
```
User Password
↓ (scrypt)
Data Key (for protected notes)
↓ (AES-128)
Protected Note Content
```
**Protected Note Metadata:**
* Content IS encrypted
* Type and MIME are NOT encrypted
* Attributes are NOT encrypted
### Data Key Management
**Key Rotation:**
* Not currently supported
* Requires re-encrypting all protected notes
### Transport Encryption
**HTTPS:**
* Recommended for server installations
* TLS 1.2+ only
* Strong cipher suites preferred
* Certificate validation enabled
**Desktop:**
* Local communication (no network)
* No HTTPS required
### Backup Encryption
**Database Backups:**
* Protected notes remain encrypted in backup
* Backup file should be protected separately
* Consider encrypting backup storage location
## Input Sanitization
### XSS Prevention
* **HTML Sanitization**
* **CKEditor Configuration:**
```
// apps/client/src/widgets/type_widgets/text_type_widget.ts
ClassicEditor.create(element, {
// Restrict allowed content
htmlSupport: {
allow: [
{ name: /./, attributes: true, classes: true, styles: true }
],
disallow: [
{ name: 'script' },
{ name: 'iframe', attributes: /^(?!src$).*/ }
]
}
})
```
* Content Security Policy
### SQL Injection Prevention
**Parameterized Queries:**
```typescript
const notes = sql.getRows(
'SELECT * FROM notes WHERE title = ?',
[userInput]
)
```
**ORM Usage:**
```typescript
// Entity-based access prevents SQL injection
const note = becca.getNote(noteId)
note.title = userInput // Sanitized by entity
note.save() // Parameterized query
```
### CSRF Prevention
**CSRF Token Validation:**
Location: `apps/server/src/routes/csrf_protection.ts`
Stateless CSRF using [Double Submit Cookie Pattern](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie) via [`csrf-csrf`](https://github.com/Psifi-Solutions/csrf-csrf).
### File Upload Validation
**Validation:**
```typescript
// Validate file size
const maxSize = 100 * 1024 * 1024 // 100 MB
if (file.size > maxSize) {
throw new Error('File too large')
}
```
## Network Security
### HTTPS Configuration
**Certificate Validation:**
* Require valid certificates in production
* Self-signed certificates allowed for development
* Certificate pinning not implemented
### Rate Limiting
**Login Rate Limiting:**
```typescript
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 10, // 10 failed attempts
skipSuccessfulRequests: true
})
app.post('/api/login/password', loginLimiter, loginHandler)
```
## Data Security
### Secure Data Deletion
**Soft Delete:**
```typescript
// Mark as deleted (sync first)
note.isDeleted = 1
note.deleteId = generateUUID()
note.save()
// Entity change tracked for sync
addEntityChange('notes', noteId, note)
```
**Hard Delete (Erase):**
```typescript
// After sync completed
sql.execute('DELETE FROM notes WHERE noteId = ?', [noteId])
sql.execute('DELETE FROM branches WHERE noteId = ?', [noteId])
sql.execute('DELETE FROM attributes WHERE noteId = ?', [noteId])
// Mark entity change as erased
sql.execute('UPDATE entity_changes SET isErased = 1 WHERE entityId = ?', [noteId])
```
**Blob Cleanup:**
```typescript
// Find orphaned blobs (not referenced by any note/revision/attachment)
const orphanedBlobs = sql.getRows(`
SELECT blobId FROM blobs
WHERE blobId NOT IN (SELECT blobId FROM notes WHERE blobId IS NOT NULL)
AND blobId NOT IN (SELECT blobId FROM revisions WHERE blobId IS NOT NULL)
AND blobId NOT IN (SELECT blobId FROM attachments WHERE blobId IS NOT NULL)
`)
// Delete orphaned blobs
for (const blob of orphanedBlobs) {
sql.execute('DELETE FROM blobs WHERE blobId = ?', [blob.blobId])
}
```
### Memory Security
**Protected Data in Memory:**
* Protected data keys stored in memory only
* Cleared on timeout
* Not written to disk
* Not in session storage
## Dependency Security
### Vulnerability Scanning
**Tools:**
* Renovate bot - Automatic dependency updates
* `pnpm audit` - Check for known vulnerabilities
* GitHub Dependabot alerts
**Process:**
```
# Check for vulnerabilities
npm audit
# Fix automatically
npm audit fix
# Manual review for breaking changes
npm audit fix --force
```
### Dependency Pinning
**package.json:**
```
{
"dependencies": {
"express": "4.18.2", // Exact version
"better-sqlite3": "^9.2.2" // Compatible versions
}
}
```
**pnpm Overrides:**
```
{
"pnpm": {
"overrides": {
"lodash@<4.17.21": ">=4.17.21", // Force minimum version
"axios@<0.21.2": ">=0.21.2"
}
}
}
```
### Patch Management
**pnpm Patches:**
```
# Create patch
pnpm patch @ckeditor/ckeditor5
# Edit files in temporary directory
# ...
# Generate patch file
pnpm patch-commit /tmp/ckeditor5-patch
# Patch applied automatically on install
```
## Security Auditing
### Logs
**Security Events Logged:**
* Login attempts (success/failure)
* Protected session access
* Password changes
* ETAPI token usage
* Failed CSRF validations
**Log Location:**
* Desktop: Console output
* Server: Log files or stdout
### Monitoring
**Metrics to Monitor:**
* Failed login attempts
* API error rates
* Unusual database changes
* Large exports/imports

View File

@ -1,17 +0,0 @@
# Documentation
## Automation
The documentation is built via `apps/build-docs`:
1. The output directory is cleared.
2. The User Guide and the Developer Guide are built.
1. The documentation from the repo is archived and imported into an in-memory instance.
2. The documentation is exported using the shared theme.
3. The API docs (internal and ETAPI) are statically rendered via Redocly.
4. The script API is generated via `typedoc`
The `deploy-docs` workflow triggers the documentation build and uploads it to CloudFlare Pages.
## Building locally
In the Git root, run `pnpm docs:build`. The built documentation will be available in `site` at Git root.

View File

@ -0,0 +1,19 @@
# Releasing a new version
Releasing is mostly handled by the CI:
* The version on GitHub is published automatically, including the description with the change log which is taken from the documentation.
* A PR is created automatically on the Winget repository to update to the new version.
Releases are usually made directly from the `main` branch.
The process is as follows:
1. Edit the <a class="reference-link" href="../Documentation.md">Documentation</a> to add a corresponding entry in the _Release notes_ section.
2. In the root `package.json`, set `version` to the new version to be released.
3. Run `chore:update-version` to automatically update the version of the rest of the `package.json` files.
4. Run `pnpm i` to update the package lock as well.
5. Commit the changes to the `package.json` files and the `package-lock.json`. The commit message is usually `chore(release): prepare for v1.2.3`.
6. Tag the newly created commit: `git tag v1.2.3`
7. Push the commit and the newly created tag: `git push; git push --tags`.
8. Wait for the CI to finish.
9. When the release is automatically created in GitHub, download it to make sure it works OK.

View File

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

View File

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@ -0,0 +1,111 @@
# Cache
### Three-Layer Cache System
Trilium implements a sophisticated **three-tier caching system** to optimize performance and enable offline functionality:
#### 1\. Becca (Backend Cache)
Located at: `apps/server/src/becca/`
```typescript
// Becca caches all entities in memory
class Becca {
notes: Record<string, BNote>
branches: Record<string, BBranch>
attributes: Record<string, BAttribute>
attachments: Record<string, BAttachment>
// ... other entity collections
}
```
**Responsibilities:**
* Server-side entity cache
* Maintains complete note tree in memory
* Handles entity relationships and integrity
* Provides fast lookups without database queries
* Manages entity lifecycle (create, update, delete)
**Key Files:**
* `becca.ts` - Main cache instance
* `becca_loader.ts` - Loads entities from database
* `becca_service.ts` - Cache management operations
* `entities/` - Entity classes (BNote, BBranch, etc.)
#### 2\. Froca (Frontend Cache)
Located at: `apps/client/src/services/froca.ts`
```typescript
// Froca is a read-only mirror of backend data
class Froca {
notes: Record<string, FNote>
branches: Record<string, FBranch>
attributes: Record<string, FAttribute>
// ... other entity collections
}
```
**Responsibilities:**
* Frontend read-only cache
* Lazy loading of note tree
* Minimizes API calls
* Enables fast UI rendering
* Synchronizes with backend via WebSocket
**Loading Strategy:**
* Initial load: root notes and immediate children
* Lazy load: notes loaded when accessed
* When note is loaded, all parent and child branches load
* Deleted entities tracked via missing branches
#### 3\. Shaca (Share Cache)
Located at: `apps/server/src/share/`
**Responsibilities:**
* Optimized cache for shared/published notes
* Handles public note access without authentication
* Performance-optimized for high-traffic scenarios
* Separate from main Becca to isolate concerns
### Cache Invalidation
**Server-Side:**
* Entities automatically update cache on save
* WebSocket broadcasts changes to all clients
* Synchronization updates trigger cache refresh
**Client-Side:**
* WebSocket listeners update Froca
* Manual reload via `froca.loadSubTree(noteId)`
* Full reload on protected session changes
### Cache Consistency
**Entity Change Tracking:**
```typescript
// Every entity modification tracked
entity_changes (
entityName: 'notes',
entityId: 'note123',
hash: 'abc...',
changeId: 'change456',
utcDateChanged: '2025-11-02...'
)
```
**Sync Protocol:**
1. Client requests changes since last sync
2. Server returns entity\_changes records
3. Client applies changes to Froca
4. Client sends local changes to server
5. Server updates Becca and database

View File

@ -0,0 +1,109 @@
# Entities
### Entity System
Trilium's data model is based on five core entities:
```
graph TD
Note[Note<br/>BNote]
Branch[Branch<br/>BBranch]
Attribute[Attribute<br/>BAttribute]
Revision[Revision<br/>BRevision]
Attachment[Attachment<br/>BAttachment]
Note -->|linked by| Branch
Note -.->|metadata| Attribute
Branch -->|creates| Revision
Note -->|has| Attachment
style Note fill:#e1f5ff
style Branch fill:#fff4e1
style Attribute fill:#ffe1f5
style Revision fill:#f5ffe1
style Attachment fill:#ffe1e1
```
#### Entity Definitions
**1\. BNote** (`apps/server/src/becca/entities/bnote.ts`)
* Represents a note with title, content, and metadata
* Type can be: text, code, file, image, canvas, mermaid, etc.
* Contains content via blob reference
* Can be protected (encrypted)
* Has creation and modification timestamps
**2\. BBranch** (`apps/server/src/becca/entities/bbranch.ts`)
* Represents parent-child relationship between notes
* Enables note cloning (multiple parents)
* Contains positioning information
* Has optional prefix for customization
* Tracks expansion state in tree
**3\. BAttribute** (`apps/server/src/becca/entities/battribute.ts`)
* Key-value metadata attached to notes
* Two types: labels (tags) and relations (links)
* Can be inheritable to child notes
* Used for search, organization, and scripting
* Supports promoted attributes (displayed prominently)
**4\. BRevision** (`apps/server/src/becca/entities/brevision.ts`)
* Stores historical versions of note content
* Automatic versioning on edits
* Retains title, type, and content
* Enables note history browsing and restoration
**5\. BAttachment** (`apps/server/src/becca/entities/battachment.ts`)
* File attachments linked to notes
* Has owner (note), role, and mime type
* Content stored in blobs
* Can be protected (encrypted)
**6\. BBlob** (`apps/server/src/becca/entities/bblob.ts`)
* Binary large object storage
* Stores actual note content and attachments
* Referenced by notes, revisions, and attachments
* Supports encryption for protected content
### Widget-Based UI
The frontend uses a **widget system** for modular, reusable UI components.
Located at: `apps/client/src/widgets/`
```typescript
// Widget Hierarchy
BasicWidget
├── NoteContextAwareWidget (responds to note changes)
│ ├── RightPanelWidget (displayed in right sidebar)
│ └── Type-specific widgets
├── Container widgets (tabs, ribbons, etc.)
└── Specialized widgets (search, calendar, etc.)
```
**Base Classes:**
1. **BasicWidget** (`basic_widget.ts`)
* Base class for all UI components
* Lifecycle: construction → rendering → events → destruction
* Handles DOM manipulation
* Event subscription management
* Child widget management
2. **NoteContextAwareWidget** (`note_context_aware_widget.ts`)
* Extends BasicWidget
* Automatically updates when active note changes
* Accesses current note context
* Used for note-dependent UI
3. **RightPanelWidget**
* Widgets displayed in right sidebar
* Collapsible sections
* Context-specific tools and information
**Type-Specific Widgets:**
Each note type has a dedicated widget, which are located in `apps/client/src/widgets/type_widgets`.

Some files were not shown because too many files have changed in this diff Show More