Merge remote-tracking branch 'origin/main' into renovate/major-vitest-monorepo

This commit is contained in:
Elian Doran
2025-11-05 21:24:10 +02:00
339 changed files with 10165 additions and 5512 deletions

View File

@@ -10,6 +10,7 @@ on:
paths:
- 'docs/**'
- 'apps/edit-docs/**'
- 'apps/build-docs/**'
- 'packages/share-theme/**'
# Allow manual triggering from Actions tab
@@ -23,6 +24,7 @@ on:
paths:
- 'docs/**'
- 'apps/edit-docs/**'
- 'apps/build-docs/**'
- 'packages/share-theme/**'
jobs:
@@ -60,6 +62,8 @@ jobs:
- name: Validate Built Site
run: |
test -f site/index.html || (echo "ERROR: site/index.html not found" && exit 1)
test -f site/developer-guide/index.html || (echo "ERROR: site/developer-guide/index.html not found" && exit 1)
echo "✓ User Guide and Developer Guide built successfully"
- name: Deploy
uses: ./.github/actions/deploy-to-cloudflare-pages

2
.nvmrc
View File

@@ -1 +1 @@
22.21.0
24.11.0

View File

@@ -38,19 +38,17 @@
"@playwright/test": "1.56.1",
"@stylistic/eslint-plugin": "5.5.0",
"@types/express": "5.0.5",
"@types/node": "24.9.1",
"@types/node": "24.10.0",
"@types/yargs": "17.0.34",
"@vitest/coverage-v8": "4.0.6",
"eslint": "9.38.0",
"eslint": "9.39.1",
"eslint-plugin-simple-import-sort": "12.1.1",
"esm": "3.2.25",
"jsdoc": "4.0.5",
"lorem-ipsum": "2.0.8",
"rcedit": "4.0.1",
"rimraf": "6.0.1",
"tslib": "2.8.1",
"typedoc": "0.28.14",
"typedoc-plugin-missing-exports": "4.1.2"
"rimraf": "6.1.0",
"tslib": "2.8.1"
},
"optionalDependencies": {
"appdmg": "0.6.6"

View File

@@ -1,15 +0,0 @@
{
"entryPoints": [
"src/services/backend_script_entrypoint.ts",
"src/public/app/services/frontend_script_entrypoint.ts"
],
"plugin": [
"typedoc-plugin-missing-exports"
],
"outputs": [
{
"name": "html",
"path": "./docs/Script API"
}
]
}

View File

@@ -0,0 +1,22 @@
{
"name": "build-docs",
"version": "1.0.0",
"description": "",
"main": "src/main.ts",
"scripts": {
"start": "tsx ."
},
"keywords": [],
"author": "Elian Doran <contact@eliandoran.me>",
"license": "AGPL-3.0-only",
"packageManager": "pnpm@10.20.0",
"devDependencies": {
"@redocly/cli": "2.11.0",
"archiver": "7.0.1",
"fs-extra": "11.3.2",
"react": "19.2.0",
"react-dom": "19.2.0",
"typedoc": "0.28.14",
"typedoc-plugin-missing-exports": "4.1.2"
}
}

View File

@@ -0,0 +1,36 @@
/**
* The backend script API is accessible to code notes with the "JS (backend)" language.
*
* The entire API is exposed as a single global: {@link api}
*
* @module Backend Script API
*/
/**
* This file creates the entrypoint for TypeDoc that simulates the context from within a
* script note on the server side.
*
* Make sure to keep in line with backend's `script_context.ts`.
*/
export type { default as AbstractBeccaEntity } from "../../server/src/becca/entities/abstract_becca_entity.js";
export type { default as BAttachment } from "../../server/src/becca/entities/battachment.js";
export type { default as BAttribute } from "../../server/src/becca/entities/battribute.js";
export type { default as BBranch } from "../../server/src/becca/entities/bbranch.js";
export type { default as BEtapiToken } from "../../server/src/becca/entities/betapi_token.js";
export type { BNote };
export type { default as BOption } from "../../server/src/becca/entities/boption.js";
export type { default as BRecentNote } from "../../server/src/becca/entities/brecent_note.js";
export type { default as BRevision } from "../../server/src/becca/entities/brevision.js";
import BNote from "../../server/src/becca/entities/bnote.js";
import BackendScriptApi, { type Api } from "../../server/src/services/backend_script_api.js";
export type { Api };
const fakeNote = new BNote();
/**
* The `api` global variable allows access to the backend script API, which is documented in {@link Api}.
*/
export const api: Api = new BackendScriptApi(fakeNote, {});

View File

@@ -4,24 +4,22 @@ process.env.NODE_ENV = "development";
import cls from "@triliumnext/server/src/services/cls.js";
import { dirname, join, resolve } from "path";
import fs, { copyFile } from "fs/promises";
import fsExtra, { createWriteStream, type WriteStream } from "fs-extra";
import * as fs from "fs/promises";
import * as fsExtra from "fs-extra";
import archiver from "archiver";
import { WriteStream } from "fs";
import { execSync } from "child_process";
import BuildContext from "./context.js";
const DOCS_ROOT = "../../../docs";
const OUTPUT_DIR = "../../site";
async function main() {
const i18n = await import("@triliumnext/server/src/services/i18n.js");
await i18n.initializeTranslations();
async function importAndExportDocs(sourcePath: string, outputSubDir: string) {
const note = await importData(sourcePath);
const sqlInit = (await import("../../server/src/services/sql_init.js")).default;
await sqlInit.createInitialDatabase(true);
const note = await importData(join(__dirname, DOCS_ROOT, "User Guide"));
// Export
const zipFilePath = "output.zip";
// Use a meaningful name for the temporary zip file
const zipName = outputSubDir || "user-guide";
const zipFilePath = `output-${zipName}.zip`;
try {
const { exportToZip } = (await import("@triliumnext/server/src/services/export/zip.js")).default;
const branch = note.getParentBranches()[0];
@@ -30,28 +28,53 @@ async function main() {
"export",
null
);
const fileOutputStream = createWriteStream(zipFilePath);
const fileOutputStream = fsExtra.createWriteStream(zipFilePath);
await exportToZip(taskContext, branch, "share", fileOutputStream);
await waitForStreamToFinish(fileOutputStream);
await extractZip(zipFilePath, OUTPUT_DIR);
// Output to root directory if outputSubDir is empty, otherwise to subdirectory
const outputPath = outputSubDir ? join(OUTPUT_DIR, outputSubDir) : OUTPUT_DIR;
await extractZip(zipFilePath, outputPath);
} finally {
if (await fsExtra.exists(zipFilePath)) {
await fsExtra.rm(zipFilePath);
}
}
}
async function buildDocsInner() {
const i18n = await import("@triliumnext/server/src/services/i18n.js");
await i18n.initializeTranslations();
const sqlInit = (await import("../../server/src/services/sql_init.js")).default;
await sqlInit.createInitialDatabase(true);
// Wait for becca to be loaded before importing data
const beccaLoader = await import("../../server/src/becca/becca_loader.js");
await beccaLoader.beccaLoaded;
// Build User Guide
console.log("Building User Guide...");
await importAndExportDocs(join(__dirname, DOCS_ROOT, "User Guide"), "user-guide");
// Build Developer Guide
console.log("Building Developer Guide...");
await importAndExportDocs(join(__dirname, DOCS_ROOT, "Developer Guide"), "developer-guide");
// Copy favicon.
await copyFile("../../apps/website/src/assets/favicon.ico", join(OUTPUT_DIR, "favicon.ico"));
await fs.copyFile("../../apps/website/src/assets/favicon.ico", join(OUTPUT_DIR, "favicon.ico"));
await fs.copyFile("../../apps/website/src/assets/favicon.ico", join(OUTPUT_DIR, "user-guide", "favicon.ico"));
await fs.copyFile("../../apps/website/src/assets/favicon.ico", join(OUTPUT_DIR, "developer-guide", "favicon.ico"));
console.log("Documentation built successfully!");
}
export async function importData(path: string) {
const buffer = await createImportZip(path);
const importService = (await import("@triliumnext/server/src/services/import/zip.js")).default;
const TaskContext = (await import("@triliumnext/server/src/services/task_context.js")).default;
const importService = (await import("../../server/src/services/import/zip.js")).default;
const TaskContext = (await import("../../server/src/services/task_context.js")).default;
const context = new TaskContext("no-progress-reporting", "importNotes", null);
const becca = (await import("@triliumnext/server/src/becca/becca.js")).default;
const becca = (await import("../../server/src/becca/becca.js")).default;
const rootNote = becca.getRoot();
if (!rootNote) {
@@ -106,4 +129,19 @@ export async function extractZip(zipFilePath: string, outputPath: string, ignore
});
}
cls.init(main);
export default async function buildDocs({ gitRootDir }: BuildContext) {
// Build the share theme.
execSync(`pnpm run --filter share-theme build`, {
stdio: "inherit",
cwd: gitRootDir
});
// Trigger the actual build.
await new Promise((res, rej) => {
cls.init(() => {
buildDocsInner()
.catch(rej)
.then(res);
});
});
}

View File

@@ -0,0 +1,4 @@
export default interface BuildContext {
gitRootDir: string;
baseDir: string;
}

View File

@@ -0,0 +1,28 @@
/**
* The front script API is accessible to code notes with the "JS (frontend)" language.
*
* The entire API is exposed as a single global: {@link api}
*
* @module Frontend Script API
*/
/**
* This file creates the entrypoint for TypeDoc that simulates the context from within a
* script note.
*
* Make sure to keep in line with frontend's `script_context.ts`.
*/
export type { default as BasicWidget } from "../../client/src/widgets/basic_widget.js";
export type { default as FAttachment } from "../../client/src/entities/fattachment.js";
export type { default as FAttribute } from "../../client/src/entities/fattribute.js";
export type { default as FBranch } from "../../client/src/entities/fbranch.js";
export type { default as FNote } from "../../client/src/entities/fnote.js";
export type { Api } from "../../client/src/services/frontend_script_api.js";
export type { default as NoteContextAwareWidget } from "../../client/src/widgets/note_context_aware_widget.js";
export type { default as RightPanelWidget } from "../../client/src/widgets/right_panel_widget.js";
import FrontendScriptApi, { type Api } from "../../client/src/services/frontend_script_api.js";
//@ts-expect-error
export const api: Api = new FrontendScriptApi();

View File

@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="refresh" content="0; url=/user-guide">
<title>Redirecting...</title>
</head>
<body>
<p>If you are not redirected automatically, <a href="/user-guide">click here</a>.</p>
</body>
</html>

View File

@@ -0,0 +1,30 @@
import { join } from "path";
import BuildContext from "./context";
import buildSwagger from "./swagger";
import { cpSync, existsSync, mkdirSync, rmSync } from "fs";
import buildDocs from "./build-docs";
import buildScriptApi from "./script-api";
const context: BuildContext = {
gitRootDir: join(__dirname, "../../../"),
baseDir: join(__dirname, "../../../site")
};
async function main() {
// Clean input dir.
if (existsSync(context.baseDir)) {
rmSync(context.baseDir, { recursive: true });
}
mkdirSync(context.baseDir);
// Start building.
await buildDocs(context);
buildSwagger(context);
buildScriptApi(context);
// 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

@@ -0,0 +1,15 @@
import { execSync } from "child_process";
import BuildContext from "./context";
import { join } from "path";
export default function buildScriptApi({ baseDir, gitRootDir }: BuildContext) {
// Generate types
execSync(`pnpm typecheck`, { stdio: "inherit", cwd: gitRootDir });
for (const config of [ "backend", "frontend" ]) {
const outDir = join(baseDir, "script-api", config);
execSync(`pnpm typedoc --options typedoc.${config}.json --html "${outDir}"`, {
stdio: "inherit"
});
}
}

View File

@@ -0,0 +1,32 @@
import BuildContext from "./context";
import { join } from "path";
import { execSync } from "child_process";
import { mkdirSync } from "fs";
interface BuildInfo {
specPath: string;
outDir: string;
}
const DIR_PREFIX = "rest-api";
const buildInfos: BuildInfo[] = [
{
// Paths are relative to Git root.
specPath: "apps/server/internal.openapi.yaml",
outDir: `${DIR_PREFIX}/internal`
},
{
specPath: "apps/server/etapi.openapi.yaml",
outDir: `${DIR_PREFIX}/etapi`
}
];
export default function buildSwagger({ baseDir, gitRootDir }: BuildContext) {
for (const { specPath, outDir } of buildInfos) {
const absSpecPath = join(gitRootDir, specPath);
const targetDir = join(baseDir, outDir);
mkdirSync(targetDir, { recursive: true });
execSync(`pnpm redocly build-docs ${absSpecPath} -o ${targetDir}/index.html`, { stdio: "inherit" });
}
}

View File

@@ -0,0 +1,36 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler",
"target": "ES2020",
"outDir": "dist",
"strict": false,
"types": [
"node",
"express"
],
"rootDir": "src",
"tsBuildInfoFile": "dist/tsconfig.app.tsbuildinfo"
},
"include": [
"src/**/*.ts",
"../server/src/*.d.ts"
],
"exclude": [
"eslint.config.js",
"eslint.config.cjs",
"eslint.config.mjs"
],
"references": [
{
"path": "../server/tsconfig.app.json"
},
{
"path": "../desktop/tsconfig.app.json"
},
{
"path": "../client/tsconfig.app.json"
}
]
}

View File

@@ -0,0 +1,15 @@
{
"extends": "../../tsconfig.base.json",
"include": [],
"references": [
{
"path": "../server"
},
{
"path": "../client"
},
{
"path": "./tsconfig.app.json"
}
]
}

View File

@@ -0,0 +1,10 @@
{
"$schema": "https://typedoc.org/schema.json",
"name": "Trilium Backend API",
"entryPoints": [
"src/backend_script_entrypoint.ts"
],
"plugin": [
"typedoc-plugin-missing-exports"
]
}

View File

@@ -0,0 +1,10 @@
{
"$schema": "https://typedoc.org/schema.json",
"name": "Trilium Frontend API",
"entryPoints": [
"src/frontend_script_entrypoint.ts"
],
"plugin": [
"typedoc-plugin-missing-exports"
]
}

View File

@@ -15,7 +15,7 @@
"circular-deps": "dpdm -T src/**/*.ts --tree=false --warning=false --skip-dynamic-imports=circular"
},
"dependencies": {
"@eslint/js": "9.38.0",
"@eslint/js": "9.39.1",
"@excalidraw/excalidraw": "0.18.0",
"@fullcalendar/core": "6.1.19",
"@fullcalendar/daygrid": "6.1.19",
@@ -37,12 +37,12 @@
"bootstrap": "5.3.8",
"boxicons": "2.1.4",
"color": "5.0.2",
"dayjs": "1.11.18",
"dayjs": "1.11.19",
"dayjs-plugin-utc": "0.1.2",
"debounce": "2.2.0",
"debounce": "3.0.0",
"draggabilly": "3.0.0",
"force-graph": "1.51.0",
"globals": "16.4.0",
"globals": "16.5.0",
"i18next": "25.6.0",
"i18next-http-backend": "3.0.2",
"jquery": "3.7.1",
@@ -59,7 +59,7 @@
"normalize.css": "8.0.1",
"panzoom": "9.4.3",
"preact": "10.27.2",
"react-i18next": "16.2.1",
"react-i18next": "16.2.4",
"reveal.js": "5.2.1",
"svg-pan-zoom": "3.6.2",
"tabulator-tables": "6.3.1",
@@ -76,7 +76,7 @@
"@types/reveal.js": "5.2.1",
"@types/tabulator-tables": "6.3.0",
"copy-webpack-plugin": "13.0.1",
"happy-dom": "20.0.8",
"happy-dom": "20.0.10",
"script-loader": "0.7.2",
"vite-plugin-static-copy": "3.1.4"
}

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

@@ -1,28 +0,0 @@
/**
* The front script API is accessible to code notes with the "JS (frontend)" language.
*
* The entire API is exposed as a single global: {@link api}
*
* @module Frontend Script API
*/
/**
* This file creates the entrypoint for TypeDoc that simulates the context from within a
* script note.
*
* Make sure to keep in line with frontend's `script_context.ts`.
*/
export type { default as BasicWidget } from "../widgets/basic_widget.js";
export type { default as FAttachment } from "../entities/fattachment.js";
export type { default as FAttribute } from "../entities/fattribute.js";
export type { default as FBranch } from "../entities/fbranch.js";
export type { default as FNote } from "../entities/fnote.js";
export type { Api } from "./frontend_script_api.js";
export type { default as NoteContextAwareWidget } from "../widgets/note_context_aware_widget.js";
export type { default as RightPanelWidget } from "../widgets/right_panel_widget.js";
import FrontendScriptApi, { type Api } from "./frontend_script_api.js";
//@ts-expect-error
export const api: Api = new FrontendScriptApi();

View File

@@ -10,7 +10,7 @@ export const byNoteType: Record<Exclude<NoteType, "book">, string | null> = {
file: null,
image: null,
launcher: null,
mermaid: null,
mermaid: "s1aBHPd79XYj",
mindMap: null,
noteMap: null,
relationMap: null,

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

@@ -716,7 +716,6 @@
"backup_database_now": "نسخ اختياطي لقاعدة البيانات الان"
},
"etapi": {
"wiki": "ويكي",
"created": "تم الأنشاء",
"actions": "أجراءات",
"title": "ETAPI",

View File

@@ -1289,10 +1289,6 @@
"etapi": {
"title": "ETAPI",
"description": "ETAPI 是一个 REST API用于以编程方式访问 Trilium 实例,而无需 UI。",
"see_more": "有关更多详细信息,请参见 {{- link_to_wiki}} 和 {{- link_to_openapi_spec}} 或 {{- link_to_swagger_ui}}。",
"wiki": "维基",
"openapi_spec": "ETAPI OpenAPI 规范",
"swagger_ui": "ETAPI Swagger UI",
"create_token": "创建新的 ETAPI 令牌",
"existing_tokens": "现有令牌",
"no_tokens_yet": "目前还没有令牌。点击上面的按钮创建一个。",

View File

@@ -1286,10 +1286,6 @@
"etapi": {
"title": "ETAPI",
"description": "ETAPI ist eine REST-API, die für den programmgesteuerten Zugriff auf die Trilium-Instanz ohne Benutzeroberfläche verwendet wird.",
"see_more": "Weitere Details können im {{- link_to_wiki}} und in der {{- link_to_openapi_spec}} oder der {{- link_to_swagger_ui }} gefunden werden.",
"wiki": "Wiki",
"openapi_spec": "ETAPI OpenAPI-Spezifikation",
"swagger_ui": "ETAPI Swagger UI",
"create_token": "Erstelle ein neues ETAPI-Token",
"existing_tokens": "Vorhandene Token",
"no_tokens_yet": "Es sind noch keine Token vorhanden. Klicke auf die Schaltfläche oben, um eine zu erstellen.",

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",
@@ -1453,10 +1456,6 @@
"etapi": {
"title": "ETAPI",
"description": "ETAPI is a REST API used to access Trilium instance programmatically, without UI.",
"see_more": "See more details in the {{- link_to_wiki}} and the {{- link_to_openapi_spec}} or the {{- link_to_swagger_ui }}.",
"wiki": "wiki",
"openapi_spec": "ETAPI OpenAPI spec",
"swagger_ui": "ETAPI Swagger UI",
"create_token": "Create new ETAPI token",
"existing_tokens": "Existing tokens",
"no_tokens_yet": "There are no tokens yet. Click on the button above to create one.",

View File

@@ -1446,10 +1446,6 @@
"etapi": {
"title": "ETAPI",
"description": "ETAPI es una REST API que se utiliza para acceder a la instancia de Trilium mediante programación, sin interfaz de usuario.",
"see_more": "Véa más detalles en el {{- link_to_wiki}} y el {{- link_to_openapi_spec}} o el {{- link_to_swagger_ui }}.",
"wiki": "wiki",
"openapi_spec": "Especificación ETAPI OpenAPI",
"swagger_ui": "ETAPI Swagger UI",
"create_token": "Crear nuevo token ETAPI",
"existing_tokens": "Tokens existentes",
"no_tokens_yet": "Aún no hay tokens. Dé clic en el botón de arriba para crear uno.",

View File

@@ -1288,8 +1288,6 @@
"etapi": {
"title": "ETAPI",
"description": "ETAPI est une API REST utilisée pour accéder à l'instance Trilium par programme, sans interface utilisateur.",
"wiki": "wiki",
"openapi_spec": "Spec ETAPI OpenAPI",
"create_token": "Créer un nouveau jeton ETAPI",
"existing_tokens": "Jetons existants",
"no_tokens_yet": "Il n'y a pas encore de jetons. Cliquez sur le bouton ci-dessus pour en créer un.",
@@ -1306,9 +1304,7 @@
"delete_token": "Supprimer/désactiver ce token",
"rename_token_title": "Renommer le jeton",
"rename_token_message": "Veuillez saisir le nom du nouveau jeton",
"delete_token_confirmation": "Êtes-vous sûr de vouloir supprimer le jeton ETAPI « {{name}} » ?",
"see_more": "Voir plus de détails dans le {{- link_to_wiki}} et le {{- link_to_openapi_spec}} ou le {{- link_to_swagger_ui }}.",
"swagger_ui": "Interface utilisateur ETAPI Swagger"
"delete_token_confirmation": "Êtes-vous sûr de vouloir supprimer le jeton ETAPI « {{name}} » ?"
},
"options_widget": {
"options_status": "Statut des options",

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.",
@@ -132,10 +133,6 @@
"new_token_message": "Inserisci il nome del nuovo token",
"title": "ETAPI",
"description": "ETAPI è un'API REST utilizzata per accedere alle istanze di Trilium in modo programmatico, senza interfaccia utente.",
"see_more": "Per maggiori dettagli consulta {{- link_to_wiki}} e {{- link_to_openapi_spec}} o {{- link_to_swagger_ui}}.",
"wiki": "wiki",
"openapi_spec": "Specifiche ETAPI OpenAPI",
"swagger_ui": "Interfaccia utente ETAPI Swagger",
"create_token": "Crea un nuovo token ETAPI",
"existing_tokens": "Token esistenti",
"no_tokens_yet": "Non ci sono ancora token. Clicca sul pulsante qui sopra per crearne uno.",

View File

@@ -657,10 +657,6 @@
"created": "作成日時",
"title": "ETAPI",
"description": "ETAPI は、Trilium インスタンスに UI なしでプログラム的にアクセスするための REST API です。",
"see_more": "詳細は{{- link_to_wiki}}と{{- link_to_openapi_spec}}または{{- link_to_swagger_ui }}を参照してください。",
"wiki": "wiki",
"openapi_spec": "ETAPI OpenAPIの仕様",
"swagger_ui": "ETAPI Swagger UI",
"create_token": "新しくETAPIトークンを作成",
"existing_tokens": "既存のトークン",
"no_tokens_yet": "トークンはまだありません。上のボタンをクリックして作成してください。",

View File

@@ -1663,10 +1663,6 @@
"etapi": {
"title": "ETAPI",
"description": "ETAPI to interfejs API REST używany do programowego dostępu do instancji Trilium, bez interfejsu użytkownika.",
"see_more": "Zobacz więcej szczegółów w {{- link_to_wiki}} oraz w {{- link_to_openapi_spec}} lub {{- link_to_swagger_ui }}.",
"wiki": "wiki",
"openapi_spec": "specyfikacja ETAPI OpenAPI",
"swagger_ui": "ETAPI Swagger UI",
"create_token": "Utwórz nowy token ETAPI",
"existing_tokens": "Istniejące tokeny",
"no_tokens_yet": "Nie ma jeszcze żadnych tokenów. Kliknij przycisk powyżej, aby utworzyć jeden.",

View File

@@ -1422,10 +1422,6 @@
"etapi": {
"title": "ETAPI",
"description": "ETAPI é uma API REST usada para aceder a instância do Trilium programaticamente, sem interface gráfica.",
"see_more": "Veja mais pormenores no {{- link_to_wiki}}, na {{- link_to_openapi_spec}} ou na {{- link_to_swagger_ui}}.",
"wiki": "wiki",
"openapi_spec": "Especificação OpenAPI do ETAPI",
"swagger_ui": "ETAPI Swagger UI",
"create_token": "Criar token ETAPI",
"existing_tokens": "Tokens existentes",
"no_tokens_yet": "Ainda não existem tokens. Clique no botão acima para criar um.",

View File

@@ -1932,10 +1932,6 @@
"etapi": {
"title": "ETAPI",
"description": "ETAPI é uma API REST usada para acessar a instância do Trilium programaticamente, sem interface gráfica.",
"see_more": "Veja mais detalhes no {{- link_to_wiki}}, na {{- link_to_openapi_spec}} ou na {{- link_to_swagger_ui}}.",
"wiki": "wiki",
"openapi_spec": "Especificação OpenAPI do ETAPI",
"swagger_ui": "ETAPI Swagger UI",
"create_token": "Criar novo token ETAPI",
"existing_tokens": "Tokens existentes",
"no_tokens_yet": "Ainda não existem tokens. Clique no botão acima para criar um.",

View File

@@ -507,17 +507,13 @@
"new_token_message": "Introduceți denumirea noului token",
"new_token_title": "Token ETAPI nou",
"no_tokens_yet": "Nu există încă token-uri. Clic pe butonul de deasupra pentru a crea una.",
"openapi_spec": "Specificația OpenAPI pentru ETAPI",
"swagger_ui": "UI-ul Swagger pentru ETAPI",
"rename_token": "Redenumește token-ul",
"rename_token_message": "Introduceți denumirea noului token",
"rename_token_title": "Redenumire token",
"see_more": "Vedeți mai multe detalii în {{- link_to_wiki}} și în {{- link_to_openapi_spec}} sau în {{- link_to_swagger_ui }}.",
"title": "ETAPI",
"token_created_message": "Copiați token-ul creat în clipboard. Trilium stochează token-ul ca hash așadar această valoare poate fi văzută doar acum.",
"token_created_title": "Token ETAPI creat",
"token_name": "Denumire token",
"wiki": "wiki"
"token_name": "Denumire token"
},
"execute_script": {
"example_1": "De exemplu, pentru a adăuga un șir de caractere la titlul unei notițe, se poate folosi acest mic script:",

View File

@@ -1440,7 +1440,6 @@
},
"etapi": {
"title": "ETAPI",
"wiki": "вики",
"created": "Создано",
"actions": "Действия",
"existing_tokens": "Существующие токены",
@@ -1448,10 +1447,7 @@
"default_token_name": "новый токен",
"rename_token_title": "Переименовать токен",
"description": "ETAPI — это REST API, используемый для программного доступа к экземпляру Trilium без пользовательского интерфейса.",
"see_more": "Более подробную информацию смотрите в {{- link_to_wiki}} и {{- link_to_openapi_spec}} или {{- link_to_swagger_ui }}.",
"create_token": "Создать новый токен ETAPI",
"openapi_spec": "Спецификация ETAPI OpenAPI",
"swagger_ui": "Пользовательский интерфейс ETAPI Swagger",
"new_token_title": "Новый токен ETAPI",
"token_created_title": "Создан токен ETAPI",
"rename_token": "Переименовать этот токен",

View File

@@ -1281,8 +1281,6 @@
"etapi": {
"title": "ETAPI",
"description": "ETAPI 是一個 REST API用於以編程方式訪問 Trilium 實例,而無需 UI。",
"wiki": "維基",
"openapi_spec": "ETAPI OpenAPI 規範",
"create_token": "新增 ETAPI 令牌",
"existing_tokens": "現有令牌",
"no_tokens_yet": "目前還沒有令牌。點擊上面的按鈕新增一個。",
@@ -1299,9 +1297,7 @@
"delete_token": "刪除 / 停用此令牌",
"rename_token_title": "重新命名令牌",
"rename_token_message": "請輸入新的令牌名稱",
"delete_token_confirmation": "您確定要刪除 ETAPI 令牌 \"{{name}}\" 嗎?",
"see_more": "有關更多詳細資訊,請參閱 {{- link_to_wiki}} 和 {{- link_to_openapi_spec}} 或 {{- link_to_swagger_ui}}。",
"swagger_ui": "ETAPI Swagger UI"
"delete_token_confirmation": "您確定要刪除 ETAPI 令牌 \"{{name}}\" 嗎?"
},
"options_widget": {
"options_status": "選項狀態",

View File

@@ -1402,10 +1402,6 @@
"etapi": {
"title": "ETAPI",
"description": "ETAPI — це REST API, який використовується для програмного доступу до екземпляра Trilium без інтерфейсу користувача.",
"see_more": "Див. докладнішу інформацію у {{- link_to_wiki}} та {{- link_to_openapi_spec}} або {{- link_to_swagger_ui }}.",
"wiki": "вікі",
"openapi_spec": "ETAPI OpenAPI spec",
"swagger_ui": "ETAPI Swagger UI",
"create_token": "Створити новий токен ETAPI",
"existing_tokens": "Існуючі токени",
"no_tokens_yet": "Токенів поки що немає. Натисніть кнопку вище, щоб створити його.",

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,14 +10,26 @@ 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 () => {
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;
@@ -29,8 +41,8 @@ export default function BranchPrefixDialog() {
return;
}
const newBranchId = await froca.getBranchId(parentNoteId, noteId);
if (!newBranchId) {
const branchId = await froca.getBranchId(parentNoteId, noteId);
if (!branchId) {
return;
}
const parentNote = await froca.getNote(parentNoteId);
@@ -38,25 +50,46 @@ export default function BranchPrefixDialog() {
return;
}
const newBranch = froca.getBranch(newBranchId);
setBranch(newBranch);
setPrefix(newBranch?.prefix ?? "");
branchIds = [branchId];
}
if (branchIds.length === 0) {
return;
}
const newBranches = branchIds
.map(id => froca.getBranch(id))
.filter((branch): branch is FBranch => branch !== null);
if (newBranches.length === 0) {
return;
}
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

@@ -41,7 +41,7 @@ export default function EditedNotesTab({ note }: TabContext) {
)}
</span>
)
}))}
}), " ")}
</div>
) : (
<div className="no-edited-notes-found">{t("edited_notes.no_edited_notes_found")}</div>

View File

@@ -2,7 +2,7 @@ import type { ComponentChildren } from "preact";
import { CSSProperties } from "preact/compat";
interface OptionsSectionProps {
title?: string;
title?: ComponentChildren;
children: ComponentChildren;
noCard?: boolean;
style?: CSSProperties;

View File

@@ -11,6 +11,7 @@ import dialog from "../../../services/dialog";
import { formatDateTime } from "../../../utils/formatters";
import ActionButton from "../../react/ActionButton";
import { useTriliumEvent } from "../../react/hooks";
import HelpButton from "../../react/HelpButton";
type RenameTokenCallback = (tokenId: string, oldName: string) => Promise<void>;
type DeleteTokenCallback = (tokenId: string, name: string ) => Promise<void>;
@@ -53,14 +54,8 @@ export default function EtapiSettings() {
return (
<OptionsSection title={t("etapi.title")}>
<FormText>
{t("etapi.description")}<br />
<RawHtml
html={t("etapi.see_more", {
link_to_wiki: `<a class="tn-link" href="https://triliumnext.github.io/Docs/Wiki/etapi.html">${t("etapi.wiki")}</a>`,
// TODO: We use window.open src/public/app/services/link.ts -> prevents regular click behavior on "a" element here because it's a relative path
link_to_openapi_spec: `<a class="tn-link" onclick="window.open('etapi/etapi.openapi.yaml')" href="etapi/etapi.openapi.yaml">${t("etapi.openapi_spec")}</a>`,
link_to_swagger_ui: `<a class="tn-link" href="#_help_f3xpgx6H01PW">${t("etapi.swagger_ui")}</a>`
})} />
{t("etapi.description")}
<HelpButton helpPage="pgxEVkzLl1OP" />
</FormText>
<Button
@@ -68,6 +63,7 @@ export default function EtapiSettings() {
text={t("etapi.create_token")}
onClick={createTokenCallback}
/>
<hr />
<h5>{t("etapi.existing_tokens")}</h5>

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

@@ -35,7 +35,7 @@
"@triliumnext/commons": "workspace:*",
"@triliumnext/server": "workspace:*",
"copy-webpack-plugin": "13.0.1",
"electron": "38.4.0",
"electron": "38.5.0",
"@electron-forge/cli": "7.10.2",
"@electron-forge/maker-deb": "7.10.2",
"@electron-forge/maker-dmg": "7.10.2",

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

@@ -12,11 +12,11 @@
"@triliumnext/desktop": "workspace:*",
"@types/fs-extra": "11.0.4",
"copy-webpack-plugin": "13.0.1",
"electron": "38.4.0",
"electron": "38.5.0",
"fs-extra": "11.3.2"
},
"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

@@ -23,6 +23,8 @@ if (!DOCS_ROOT || !USER_GUIDE_ROOT) {
throw new Error("Missing DOCS_ROOT or USER_GUIDE_ROOT environment variable.");
}
const BASE_URL = "https://docs.triliumnotes.org";
const NOTE_MAPPINGS: NoteMapping[] = [
{
rootNoteId: "pOsGYCXsbNQG",
@@ -158,6 +160,14 @@ async function cleanUpMeta(outputPath: string, minify: boolean) {
}
el.isExpanded = false;
// Rewrite web view URLs that point to root.
if (el.type === "webView" && minify) {
const srcAttr = el.attributes.find(attr => attr.name === "webViewSrc");
if (srcAttr.value.startsWith("/")) {
srcAttr.value = BASE_URL + srcAttr.value;
}
}
}
if (minify) {

View File

@@ -1,4 +1,4 @@
FROM node:24.10.0-bullseye-slim AS builder
FROM node:24.11.0-bullseye-slim AS builder
RUN corepack enable
# Install native dependencies since we might be building cross-platform.
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
# We have to use --no-frozen-lockfile due to CKEditor patches
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
FROM node:24.10.0-bullseye-slim
FROM node:24.11.0-bullseye-slim
# Install only runtime dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends \

View File

@@ -1,4 +1,4 @@
FROM node:24.10.0-alpine AS builder
FROM node:24.11.0-alpine AS builder
RUN corepack enable
# Install native dependencies since we might be building cross-platform.
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
# We have to use --no-frozen-lockfile due to CKEditor patches
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
FROM node:24.10.0-alpine
FROM node:24.11.0-alpine
# Install runtime dependencies
RUN apk add --no-cache su-exec shadow

View File

@@ -1,4 +1,4 @@
FROM node:24.10.0-alpine AS builder
FROM node:24.11.0-alpine AS builder
RUN corepack enable
# Install native dependencies since we might be building cross-platform.
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
# We have to use --no-frozen-lockfile due to CKEditor patches
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
FROM node:24.10.0-alpine
FROM node:24.11.0-alpine
# Create a non-root user with configurable UID/GID
ARG USER=trilium
ARG UID=1001

View File

@@ -1,4 +1,4 @@
FROM node:24.10.0-bullseye-slim AS builder
FROM node:24.11.0-bullseye-slim AS builder
RUN corepack enable
# Install native dependencies since we might be building cross-platform.
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
# We have to use --no-frozen-lockfile due to CKEditor patches
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
FROM node:24.10.0-bullseye-slim
FROM node:24.11.0-bullseye-slim
# Create a non-root user with configurable UID/GID
ARG USER=trilium
ARG UID=1001

View File

@@ -4,12 +4,12 @@ info:
title: ETAPI
description: External Trilium API
contact:
name: zadam
email: zadam.apps@gmail.com
url: https://github.com/zadam/trilium
name: Trilium Notes Team
email: contact@eliandoran.me
url: https://triliumnotes.org
license:
name: Apache 2.0
url: https://www.apache.org/licenses/LICENSE-2.0.html
name: GNU Affero General Public License v3.0 only
url: https://www.gnu.org/licenses/agpl-3.0.en.html
servers:
- url: http://localhost:37740/etapi
- url: http://localhost:8080/etapi

View File

@@ -1,7 +1,7 @@
openapi: 3.1.0
info:
title: Trilium Notes Internal API
version: 0.98.0
title: Internal Trilium API
version: 0.99.3
description: |
This is the internal API used by the Trilium Notes client application.
@@ -24,11 +24,12 @@ info:
State-changing operations require CSRF tokens when using session authentication.
contact:
name: TriliumNext Issue Tracker
url: https://github.com/TriliumNext/Trilium/issues
name: Trilium Notes Team
email: contact@eliandoran.me
url: https://triliumnotes.org
license:
name: GNU Affero General Public License v3.0
url: https://www.gnu.org/licenses/agpl-3.0.html
name: GNU Affero General Public License v3.0 only
url: https://www.gnu.org/licenses/agpl-3.0.en.html
servers:
- url: http://localhost:8080

View File

@@ -30,7 +30,7 @@
"node-html-parser": "7.0.1"
},
"devDependencies": {
"@anthropic-ai/sdk": "0.67.0",
"@anthropic-ai/sdk": "0.68.0",
"@braintree/sanitize-url": "7.1.1",
"@electron/remote": "2.1.3",
"@preact/preset-vite": "2.10.2",
@@ -61,35 +61,34 @@
"@types/serve-static": "2.2.0",
"@types/stream-throttle": "0.1.4",
"@types/supertest": "6.0.3",
"@types/swagger-ui-express": "4.1.8",
"@types/tmp": "0.2.6",
"@types/turndown": "5.0.6",
"@types/ws": "8.18.1",
"@types/xml2js": "0.4.14",
"archiver": "7.0.1",
"async-mutex": "0.5.0",
"axios": "1.13.0",
"axios": "1.13.2",
"bindings": "1.5.0",
"bootstrap": "5.3.8",
"chardet": "2.1.0",
"chardet": "2.1.1",
"cheerio": "1.1.2",
"chokidar": "4.0.3",
"cls-hooked": "4.2.2",
"compression": "1.8.1",
"cookie-parser": "1.4.7",
"csrf-csrf": "3.2.2",
"dayjs": "1.11.18",
"debounce": "2.2.0",
"dayjs": "1.11.19",
"debounce": "3.0.0",
"debug": "4.4.3",
"ejs": "3.1.10",
"electron": "38.4.0",
"electron": "38.5.0",
"electron-debug": "4.1.0",
"electron-window-state": "5.0.3",
"escape-html": "1.0.3",
"express": "5.1.0",
"express-http-proxy": "2.1.2",
"express-openid-connect": "2.19.2",
"express-rate-limit": "8.1.0",
"express-rate-limit": "8.2.1",
"express-session": "1.18.2",
"file-uri-to-path": "2.0.0",
"fs-extra": "11.3.2",
@@ -110,20 +109,19 @@
"mime-types": "3.0.1",
"multer": "2.0.2",
"normalize-strings": "1.1.1",
"ollama": "0.6.0",
"openai": "6.7.0",
"ollama": "0.6.2",
"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",
"striptags": "3.2.0",
"supertest": "7.1.4",
"swagger-jsdoc": "6.2.8",
"swagger-ui-express": "5.0.1",
"time2fa": "1.4.2",
"tmp": "0.2.5",
"turndown": "7.2.2",

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -36,7 +36,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>.
@@ -145,17 +145,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/_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%;">
<p>If you don't see the button in the&nbsp;<a class="reference-link" href="#root/_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

@@ -1,6 +1,8 @@
<aside class="admonition tip">
<p>For a quick start, consult the&nbsp;<a class="reference-link" href="#root/_help_9qPsTWBorUhQ">API Reference</a>.</p>
</aside>
<p>ETAPI is Trilium's public/external REST API. It is available since Trilium
v0.50.</p>
<p>The documentation is in OpenAPI format, available <a href="https://github.com/TriliumNext/Trilium/blob/master/src/etapi/etapi.openapi.yaml">here</a>.</p>
<h2>API clients</h2>
<p>As an alternative to calling the API directly, there are client libraries
to simplify this</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,41 @@
<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/_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/_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/_help_eIg8jdvaoNNd">Search</a>&nbsp;or
the interactivity behind&nbsp;<a class="reference-link" href="#root/_help_GTwFsgaA0lCt">Collections</a>&nbsp;or
the various&nbsp;<a class="reference-link" href="#root/_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>All notes are read-only, without the possibility of editing them.</li>
<li>Features that would normally alter the database such as the list of recent
notes are disabled.</li>
</ul>
<h2>Limitations</h2>
<ul>
<li>Some features might “slip through” and still end up creating a note, for
example.
<ul>
<li>However, the database is still read-only, so all modifications will be
reset if the server is restarted.</li>
<li>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/_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

@@ -0,0 +1,12 @@
<p>Safe mode is triggered by setting the <code>TRILIUM_SAFE_MODE</code> environment
variable to a truthy value, usually <code>1</code>.</p>
<p>In each artifact there is a <code>trilium-safe-mode.sh</code> (or <code>.bat</code>)
script to enable it.</p>
<p>What it does:</p>
<ul>
<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

@@ -41,6 +41,15 @@
<li>The export requires a functional web server as the pages will not render
properly if accessed locally via a web browser due to the use of module
scripts.</li>
<li>The directory structure is also slightly different:
<ul>
<li>A normal HTML export results in an index file and a single directory.</li>
<li>Instead, for static exporting the top-root level becomes the index file
and the child directories are on the root instead.</li>
<li>This makes it possible to easily publish to a website, without forcing
everything but the root note to be in a sub-directory.</li>
</ul>
</li>
</ul>
<h2>Testing locally</h2>
<p>As mentioned previously, the exported static pages require a website to

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

@@ -28,20 +28,27 @@
- 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>Press the Windows key on your keyboard.</li>
<li>Search and select “Edit the system variables”.</li>
<li>Press the “Environment Variables…” button in the bottom-right of the newly
opened screen.</li>
<li>On the top section ("User variables for [user]"), press the “New…” button.</li>
<li>In the <em>Variable name</em> field insert <code>TRILIUM_DATA_DIR</code>.</li>
<li>Press the <em>Browse Directory…</em> button and select the new directory
where to store the database.</li>
<li>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,16 +57,13 @@
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>

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

@@ -0,0 +1,64 @@
<p>Since TriliumNext 0.94.1, the desktop and server applications can be built
using <a href="https://nixos.org/">Nix</a>.</p>
<h2>System requirements</h2>
<p>Installation of Nix on Mac or Linux (<a href="https://nixos.org/download/">download page</a>).
About 3-4 gigabytes of additional storage space, for build artifacts.</p>
<h2>Run directly</h2>
<p>Using <a href="https://nix.dev/manual/nix/stable/command-ref/new-cli/nix3-run.html">nix run</a>,
the desktop app can be started as: <code>nix run github:TriliumNext/Trilium/v0.95.0</code>
</p>
<p>Running the server requires explicitly specifying the desired package: <code>nix run github:TriliumNext/Trilium/v0.95.0#server</code>
</p>
<p>Instead of a version (<code>v0.95.0</code> above), you can also specify
a commit hash (or a branch name). This makes it easy to test development
builds.</p>
<h2>Install on NixOS</h2>
<p>Add to your <code>flake.nix</code>:</p><pre><code class="language-text-x-trilium-auto">{
inputs = {
nixpkgs.url = # ...;
trilium-notes = {
url = "github:TriliumNext/Trilium/v0.95.0";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs =
{
self,
# ...
trilium-notes,
...
}:
{
nixosConfigurations = {
"nixos" = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./configuration.nix
];
specialArgs = {
inherit
trilium-notes
;
};
};
};
};
}
</code></pre>
<p>Add to your <code>configuration.nix</code>:</p><pre><code class="language-text-x-trilium-auto">{
# ...
trilium-notes,
...
}:
{
# ...
services.trilium-server.package = trilium-notes.packages.x86_64-linux.server;
environment.systemPackages = [
trilium-notes.packages.x86_64-linux.desktop
];
}</code></pre>
<p>The flake aims to be compatible with the latest NixOS stable and unstable.</p>

View File

@@ -1,21 +1,21 @@
<p>The desktop version of Trilium supports all three main operating systems:</p>
<ul>
<li data-list-item-id="e9369fe00e2202eddd7638f9115592204">Windows
<li>Windows
<ul>
<li data-list-item-id="ecbf2fbdd331d51182332dd3d33aeab15">Windows 11 is officially supported.</li>
<li data-list-item-id="e029be961ae70031059602696f29f72bf">Windows on ARM is also supported</li>
<li>Windows 11 is officially supported.</li>
<li>Windows on ARM is also supported</li>
</ul>
</li>
<li data-list-item-id="e87f0cd97f723e8bd59f181622122906b">Linux:
<li>Linux:
<ul>
<li data-list-item-id="efa1b52fdb55980bee3f3482ee0f55dfa">Most modern distributions are supported, including NixOS.</li>
<li data-list-item-id="efe26984776dbcafc56afcc83a4d1b85b">ARM is supported in <code>aarch64</code> (no ARM v7 support).&nbsp;</li>
<li>Most modern distributions are supported, including NixOS.</li>
<li>ARM is supported in <code>aarch64</code> (no ARM v7 support).</li>
</ul>
</li>
<li data-list-item-id="eabb3c5c35295997be8b2579caa6cae1b">macOS
<li>macOS
<ul>
<li data-list-item-id="e2848f18f02ba6ded3f20f46e12fecc54">Minimum supported operating system: macOS Monterey&nbsp;</li>
<li data-list-item-id="ea709467c48e79ae0c7e4d3c5e9261505">Both Intel and Apple Silicon devices are supported.</li>
<li>Minimum supported operating system: macOS Monterey</li>
<li>Both Intel and Apple Silicon devices are supported.</li>
</ul>
</li>
</ul>

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

@@ -0,0 +1 @@
<p>This is a clone of a note. Go to its <a href="../Desktop%20Installation/Nix%20flake.html">primary location</a>.</p>

View File

@@ -1,7 +1,6 @@
<ul>
<li data-list-item-id="edfd91483e74d0b747136f134131b9720">Using Docker, the server can be run on Windows, Linux and macOS devices.</li>
<li
data-list-item-id="efde3b56d3d4f5b167bad52f55637e111">Native binaries are provided for Linux x64 and ARM (<code>aarch64</code>).</li>
<li>Using Docker, the server can be run on Windows, Linux and macOS devices.</li>
<li>Native binaries are provided for Linux x64 and ARM (<code>aarch64</code>).</li>
</ul>
<h2>Legacy ARM support</h2>
<p>The Docker builds also provide <code>linux/arm/v7</code> and <code>linux/arm/v8</code> platforms.

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

View File

@@ -0,0 +1,301 @@
<aside class="admonition note">
<p>This article is a description of the original author of Trilium (zadam)
in regards with his own knowledge base.</p>
</aside>
<p>This page contains description of some of the patterns I use to organize
information in my knowledge base. This is meant to give some inspiration
of how one might create and structure their knowledge base in general and
also specifically in Trilium Notes. It also gives some background and justification
for some of the design decisions.</p>
<h2>Meta patterns</h2>
<p>Just to be clear, meta patterns are "patterns of patterns", i.e. patterns
appearing in other patterns.</p>
<h3>Hierarchical organization of information</h3>
<p>Basic meta pattern is that I sort notes (units of information) into a
hierarchy - I have some "top level" notes which represent coarse grained
organization, these then split into sub-notes defining finer grained organization
and so on. I consider this hierarchical (tree) organization very efficient
for organization of large amounts of information. A lot of note taking
software (such as Evernote) are frustratingly limited in this regard which
limits scalability of the software to large amounts of notes.</p>
<h4>Scalability</h4>
<p>It's important to frame the following (meta) patterns with some idea of
how large amount of data are we talking about.</p>
<p>My rule of thumb for estimation of size of personal knowledge base is
that you can reasonably produce around 10 notes a day, which is 3650 in
a year. I plan to use my knowledge base long term (with or without Trilium
Notes), probably decades so you can easily get to number 100 000 or even
more. Right now, my personal knowledge base has around 10 000 notes.</p>
<p>100 000 is a number to which most note taking software doesn't scale well
(in both performance and UI). Yet I don't think it's really very much considering
a lifetime of knowledge.</p>
<h4>Lazy hierarchy</h4>
<p>My approach to creating the hierarchy is being lazy - I don't create the
structure first and then fill it with notes, instead I create single note
for some specific topic and start using this one note. Once the content
starts to grow, and I see how <em>some</em> parts could be split out, I move
them out into separate sub notes. As an example I have a book review for <em>The Fellowship of the Ring</em>:</p>
<ul>
<li>Book reviews
<ul>
<li>The Fellowship of the Ring</li>
</ul>
</li>
</ul>
<p>The note contains basic book info (author, publisher etc.), book highlights
with the comments and then overall review. Now it turns out there's far
too many book highlights and overall review is also rather long, so I want
to change the structure to the following:</p>
<ul>
<li>Book reviews
<ul>
<li>The Fellowship of the Ring &nbsp; &nbsp; &nbsp;&nbsp;<em>(still contains basic info)</em>
<ul>
<li>Highlights</li>
<li>Review</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>If I used standard text file stored in a filesystem I would soon run into
an annoying problem that in order to split out the Highlights and Review
into sub-notes I would also have to convert <em>The Fellowship of the Ring</em> from
text file into directory and split out all sections of the note into sub-notes.
Instead, Trilium treats all notes as equal - both leaf notes and inner
notes can have both text content which allows me to sub-structure only
content which needs it.</p>
<h3>Sorting notes into multiple places in the hierarchy</h3>
<p>While organizing the notes into the hierarchy, you very quickly run into
a dilemma - your note seem to belong to two places in the hierarchy equally.
As an example - you want to make a note about <a href="https://en.wikipedia.org/wiki/Bash_(Unix_shell)">bash</a> -
does it belong to "OS / Linux" or "Programming / Scripting languages"?
This is actually a false dichotomy forced down by the limits of the basic
tree hierarchy - the answer is <em>of course it belongs to both</em>. This
is the reason why Trilium doesn't use standard tree structure (which requires
every note to have exactly one parent), but an extension which allows every
note to have several parents, thus effectively allowing it to appear in
multiple places in the hierarchy. For lack of better term I call this "cloning".
The main problem with this term is that it suggests that each clone must
have an original, but here all clones are completely equal - effectively
there's no original.</p>
<p>In tech lingo, it might be better to describe it as a <a href="https://en.wikipedia.org/wiki/Hard_link">hard link</a> with
an important difference that it is possible to hard link (clone) a directory
(inner note).</p>
<h3>Protected notes</h3>
<p>I have Trilium Notes opened non-stop. Sometimes I forget to lock my computer
when going to the bathroom. Sometimes I let a friend or family member to
use my computer for a minute without supervision. They might click on (running)
Trilium and inadvertently see a note I really don't want anybody to see
(personal diary, credentials). To cover this, Trilium has a concept of
"<a href="https://github.com/zadam/trilium/wiki/Protected-notes">protected notes</a>"
- protected note is encrypted and on top of that requires the user to enter
the password every 5 minutes which guarantees that such note can be in
a readable state only for small amount of time. Working with ordinary (not
protected) notes don't require password so you're not bothered by extra
security when it's not needed.</p>
<h3>Archiving notes</h3>
<p>Notes can lose relevancy with time - let's say I switch jobs - all the
notes specific to the former employer immediately lose most of its import.
This doesn't mean I want to delete these notes though - typically I just
want them to somehow deprioritize - in Trilium I would do that by assigning
an <a href="https://github.com/zadam/trilium/wiki/Attribute-inheritance">inherited</a>
<a
href="https://github.com/zadam/trilium/wiki/Attributes">label</a> <code>archived</code> to the company root note. The main effect
of this label is that all the notes from this sub-tree are filtered out
from search results (fast search via note autocomplete is my main <a href="https://github.com/zadam/trilium/wiki/Note-navigation">navigation approach</a>).
Apart from this, I also typically move such outdated notes to some less
prominent place in the hierarchy.</p>
<p>I use archivation also for notes which are not very relevant from their
creation - an example might be automatically imported reddit comments.</p>
<p>Sometimes there's no clear <em>category</em> split between relevant and
non-relevant notes, in that case I just create "<em>OLD</em>" note with <code>archived</code> label
and move all irrelevant notes there. So my credentials note might look
something like this:</p>
<ul>
<li>Credentials
<ul>
<li>Personal
<ul>
<li>OLD &nbsp; &nbsp; &nbsp;&nbsp;<em>(contains a bunch of notes with credentials for services I don't use anymore)</em>
</li>
<li>Gmail</li>
<li>Github</li>
<li>...</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2>Patterns</h2>
<h3>Day note</h3>
<p>Every day has its note which contains or references everything related
to the given day. Structure looks like this:</p>
<ul>
<li>2018
<ul>
<li>11 - November
<ul>
<li>26 - Monday</li>
<li>27 - Tuesday
<ul>
<li>subnote 1</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>Day note serves as a workspace and note inbox at the same time - it's
the default location to create a note when I don't have time to think about
proper placement. At the end of the day I typically review my day note
and clone the notes into suitable locations in the hierarchy.</p>
<p>Trilium has this pattern partly built-in - Trilium understands and can
create this Year / Month / Day structure semi-automatically (on API call).
There's also global keyboard shortcut <code>CTRL-ALT-P</code> which will
create new note in the day note.</p>
<p>What notes do I keep under this day note?</p>
<ul>
<li>TODO list for given day (this can be automated - see&nbsp;<a class="reference-link"
href="#root/_help_xYjQUYhpbUEW">Task Manager</a>)</li>
<li>Personal diary</li>
<li><a href="#root/_help_IakOLONlIfGI">clones</a> of notes I created during this
day (which kind of represents what I've been working on).</li>
<li>I often clone notes (or sub-trees) of e.g. projects I'm working on at
given day so they are at hand</li>
<li>I have some <a href="#root/_help_CdNpE2pqjmI6">scripts</a> which allow me to track
certain daily metrics (like weight). These are saved into one daily "data
note" (actually JSON <a href="#root/_help_6f9hih2hXXZk">code note</a>).
<ul>
<li>I have other scripts which then help me to visualize these data (see a&nbsp;
<a
class="reference-link" href="#root/_help_R7abl2fc6Mxi">Weight Tracker</a>&nbsp;example)</li>
<li>I have a script which automatically imports all my comments from reddit
into the day note.
<ul>
<li>People are sometimes wondering why. The answer is that I usually put some
effort and thought into a comment and that's why I feel it's worth preserving,
especially if it can be done automatically.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>For most notes, this day note placement is <em>secondary</em> and their
primary location is somewhere else (e.g. for a book review I've been working
on it's <em>Book / Reviews</em>, not the day note). So for this pattern
to work, ability to <a href="#root/_help_IakOLONlIfGI">clone</a> notes into multiple
places is pretty fundamental.</p>
<h3>Projects</h3>
<p><em>Project</em> is pretty self-explanatory, for me specifically it also
means being long term (years) - an example of a project might be Trilium
Notes or university studies. Given their longevity, projects can be large
and deep, but their structure is very domain specific, and I don't see
any common patterns. What's pretty clear is they are often widely interconnected
with other parts of the knowledge base - e.g. university credentials are
cloned from "Credentials / University" top level notes and Trilium related
blog posts are in "Blog / [Name of the blog] / Trilium".</p>
<p><em>Epics</em> are the same thing as projects, but differ in scope - they
are typically several months long and as such are usually placed into a
year note (e.g. <em>2018 / Epics</em>). Epics are often of work nature (also
cloned into work note) and personal (e.g. currently I have large epic for
moving to a different city).</p>
<p>I don't have a term for short term projects (typically several days long),
but continuing the scrum analogy I might call them <em>story</em>. These
are often placed directly into day notes and manually moved from one day
to another (or place into a month note, e.g. <em>2018 / 11 - November</em>).</p>
<h3>Credentials</h3>
<p>I keep all my credentials in the knowledge base, they are sorted into
categories - work related, project related, personal per country etc. These
notes are of course <a href="#root/_help_bwg0e8ewQMak">protected</a> and are often
cloned into other places (e.g. project credentials are cloned into the
project itself). This is a pretty important advantage compared to traditional
tools like KeePass - all the relevant information is centralized into one
place without compromising security.</p>
<h3>People profiles</h3>
<p>This might seem creepy to some, but I keep a profile on most people. It
contains pretty standard things like date of birth, contacts, address,
but also current and previous employments, their hobbies and worldviews
and sometimes even important (IM/mail/meatspace) conversations. Just about
everything I find notable. It helps to refresh some basic info before meeting
people, especially if you haven't been in touch in a while. It gets pretty
awkward to ask for the tenth time where do they work for example, because
you keep forgetting it.</p>
<p>Naturally I have a lot of (extended) family members, friends, acquaintances
etc. so I need some way to sort them. My main method is to sort them by
social circle (work, high school, sports club etc.), sometimes also by
their town of residence. Family <em>circle</em> is still too large so the
further organization is by <em>clan</em> (as in "Smiths"). Some people are
members of several such circles, so they are just cloned into multiple
places.</p>
<p>For family specifically it's pretty useful to create <a href="#root/_help_iRwzGnHPzonm">relation map</a> to
visualize relationships:</p>
<figure class="image">
<img style="aspect-ratio:941/758;" src="Patterns of personal knowl.png"
width="941" height="758">
</figure>
<p><a class="reference-link" href="#root/_help_lQcnSv5DMSe1">[missing note]</a>
</p>
<h3>Books</h3>
<p>Of course, I keep standard "To read" list. I also keep a record on the
books I've read - typically one book has one subtree where the root has
some basic info like author, page count, publication date, date started,
date finished (in the form of&nbsp;<a class="reference-link" href="#root/_help_OFXdgB2nNk1F">Promoted Attributes</a>).
I also write a (private) review and keep list of highlights from Kindle,
optionally with some commentary, these are usually stored in sub notes
(unless they are pretty short).</p>
<p>To keep the list of books manageable, I sort them per year (of reading
them), this also gives me some basic overview of "reading performance"
for given year. I plan to create a <a href="#root/_help_CdNpE2pqjmI6">script</a> which
would show some timeline chart visualizing book attributes <code>dateStarted</code> - <code>dateFinished</code> to
have nicer view of my reading sprints and trends.</p>
<p>Some specific authors also have their own note which contains cloned book
reviews, links to interviews and other related resources.</p>
<p>I have similar system for movies and TV shows, but not as sophisticated.</p>
<h3>Personal diary</h3>
<p>This is a place to reflect on events, experiences, new findings etc. This
can help you get deeper understanding of your inner self, clarify your
thinking and make better decisions as a result.</p>
<p>I sort personal diary notes directly under <em>day note</em> (explained
above), but it can be cloned also to e.g. "trip note" (if the diary note
is about given trip) or to person's profile (if the person plays a role
in the diary note). All my diary notes are <a href="#root/_help_bwg0e8ewQMak">protected</a> since
they are usually pretty sensitive.</p>
<h3>Documents</h3>
<p>I keep all my personal documents (ID, passport, education certificates
...) scanned in the knowledge base. They are <a href="#root/_help_cbkrhQjrkKrh">synchronized</a> across
every PC which provides decent backup and makes them available everywhere.</p>
<p>Advantage compared to e.g. keeping them in Dropbox or Google Drive is
that they are not stored on some 3rd party server and they can be encrypted
(<a href="#root/_help_bwg0e8ewQMak">protected</a>).</p>
<h3>Inventory</h3>
<p>Inventory contains documents and other relevant importation for my important
belongings - e.g. for car you can keep the registration card, maintenance
record, related costs etc. I also keep inventory for some items personally
important to me - mainly computers, phones, cameras and similar electronics.
This can be practical at times but also provides sentimental value.</p>
<h3>Topic knowledge base</h3>
<p>This where I store hard "knowledge" - summarized topics and findings from
different domains. Topics can range from traditional sciences - physics,
history, economy to philosophy, mental models, apps (notes about specific
apps I use) etc. Of course this is very subjective - given what I do, my
Physics sub-tree is pretty sparse compared to my Programming subtree.</p>
<h3>Work knowledge base</h3>
<p>I usually keep top level note for the company I currently work at (past
jobs are moved elsewhere). I track basic organization of the company (divisions,
business units), who is who (<a href="#root/_help_iRwzGnHPzonm">relation maps</a>)
are again useful for visualization), projects I work at etc.</p>
<p>There's a number of credentials to various company services I need to
use. Companies usually have a bunch of complex processes and tools. I record
meeting minutes, link to the company wiki (which is usually difficult to
find relevant info). In general there's a lot of company specific information
I need to know or need have them at hand in a nice structure I can understand.
Often it's just copy pasting and reshuffling of existing information into
something more understandable for me.</p>
<p>From my experience, keeping this makes me more productive and even more
importantly dramatically reduces frustration and stress.</p>
<h2>Conclusion</h2>
<p>I could probably go on with more patterns (e.g. study notes, travelling),
but I think you get the idea. Whatever is important in your life, it probably
makes sense to document and track it.</p>

View File

@@ -0,0 +1,20 @@
<h3>Trilium Notes</h3>
<p>Trilium Notes does not collect/send any data from the user's installation,
i.e. no analytics, no telemetry etc. The data flows only between user controlled
/ installed applications, without any intermediary.</p>
<p>Automatic network activity consists of:</p>
<ul>
<li>Trilium periodically queries URL <a href="https://github.com/TriliumNext/Trilium/releases">https://github.com/TriliumNext/Trilium/releases</a> to
see if there's a new stable version released. (check only, there's no automatic
download and/or installation).</li>
<li>Trilium will download spelling dictionaries automatically as needed based
on language settings</li>
</ul>
<h3>Trilium Web Clipper</h3>
<p>Trilium Web Clipper does not collect/send any data from the user's installation,
i.e. no analytics, no telemetry etc. The data flows only between user controlled
/ installed applications, without any intermediary.</p>
<h3>Trilium Sender for Android</h3>
<p>Trilium Sender for Android does not collect/send any data from the user's
installation, i.e. no analytics, no telemetry etc. The data flows only
between user controlled / installed applications, without any intermediary.</p>

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

@@ -1,3 +1,7 @@
<aside class="admonition tip">
<p>For a quick understanding of the Mermaid syntax, see&nbsp;<a class="reference-link"
href="#root/_help_WWgeUaBb7UfC">Syntax reference</a>&nbsp;(official documentation).</p>
</aside>
<figure class="image image-style-align-center">
<img style="aspect-ratio:886/663;" src="2_Mermaid Diagrams_image.png"
width="886" height="663">
@@ -6,7 +10,6 @@
as flowchart, sequence diagram, class diagram, state diagram, pie charts,
etc., all using a text description of the chart instead of manually drawing
the diagram.</p>
<p>For the official documentation of Mermaid.js see <a href="https://mermaid.js.org/intro/">mermaid.js.org/intro/</a>.</p>
<h2>Layouts</h2>
<p>Depending on the chart being edited and user preference, there are two
layouts supported by the Mermaid note type:</p>

View File

@@ -8,59 +8,58 @@
the desired amount of columns and rows, as indicated in the adjacent figure.</p>
<h2>Formatting toolbar</h2>
<p>When a table is selected, a special formatting toolbar will appear:</p>
<p>
<img src="10_Tables_image.png" width="384"
<img
src="10_Tables_image.png" width="384"
height="100">
</p>
<h2>Navigating a table</h2>
<ul>
<li data-list-item-id="e7f46134b097590227ab8def4844112be">Using the mouse:
<li>Using the mouse:
<ul>
<li data-list-item-id="e62455762d7530b06d496e0ddb23d2e0f">Click on a cell to focus it.</li>
<li data-list-item-id="e5b8037a928ee05c9137083da09f3c5a4">Click the
<li>Click on a cell to focus it.</li>
<li>Click the
<img src="11_Tables_image.png"
width="28" height="27">button at the top or the bottom of a table to insert an empty paragraph
near it.</li>
<li data-list-item-id="e1d54454a8e1cbe7d5e29d7e262374052">Click the
<li>Click the
<img src="5_Tables_image.png"
width="24" height="26">button at the top-left of the table to select it entirely (for easy copy-pasting
or cutting) or drag and drop it to relocate the table.</li>
</ul>
</li>
<li data-list-item-id="eb3673372bf61d20c70aa4787bbb1db3b">Using the keyboard:
<li>Using the keyboard:
<ul>
<li data-list-item-id="e4eb9ecdc5852d23b7e1b76be1bffe8cf">Use the arrow keys on the keyboard to easily navigate between cells.</li>
<li
data-list-item-id="e010d5a3a937c26f80367271d69687ab6">It's also possible to use <kbd>Tab</kbd> to go to the next cell and Shift+Tab
<li>Use the arrow keys on the keyboard to easily navigate between cells.</li>
<li>It's also possible to use <kbd>Tab</kbd> to go to the next cell and Shift+Tab
to go to the previous cell.</li>
<li data-list-item-id="e5c75e2a259cd2d0aa1252f2452c28fd6">Unlike arrow keys, pressing <kbd>Tab</kbd> at the end of the table (last
<li>Unlike arrow keys, pressing <kbd>Tab</kbd> at the end of the table (last
row, last column) will create a new row automatically.</li>
<li data-list-item-id="e86d89dfc2818e132bc6d7f7f48295dba">To select multiple cells, hold <kbd>Shift</kbd> while using the arrow keys.</li>
<li>To select multiple cells, hold <kbd>Shift</kbd> while using the arrow keys.</li>
</ul>
</li>
</ul>
<h2>Resizing cells</h2>
<ul>
<li data-list-item-id="e327223f3de605b4ab196102167f22219">Columns can be resized by hovering the mouse over the border of two adjacent
<li>Columns can be resized by hovering the mouse over the border of two adjacent
cells and dragging it.</li>
<li data-list-item-id="e2a9150da21e9bd9ab3134c44c016b5cf">By default, the row height is not adjustable using the mouse, but it can
<li>By default, the row height is not adjustable using the mouse, but it can
be configured from the cell settings (see below).</li>
<li data-list-item-id="ee8e8edc219006eec516c1e813494ddb1">To adjust exactly the width (in pixels or percentages) of a cell, select
<li>To adjust exactly the width (in pixels or percentages) of a cell, select
the
<img src="8_Tables_image.png" width="19"
height="19">button.</li>
</ul>
<h2>Inserting new rows and new columns</h2>
<ul>
<li data-list-item-id="ebf4db28ad263fbce13bf056e90512914">To insert a new column, click on a desired location, then press the
<li>To insert a new column, click on a desired location, then press the
<img
src="Tables_image.png" width="18" height="20">button from the formatting toolbar and select <em>Insert column left or right.</em>
</li>
<li data-list-item-id="eb53e629e394d8a14c48dd39a61397271">To insert a new row, click on a desired location, then press the
<li>To insert a new row, click on a desired location, then press the
<img src="7_Tables_image.png"
width="20" height="18">button and select <em>Insert row above</em> or <em>below</em>.
<ul>
<li data-list-item-id="e3ba47144529996c9e4455970508a0a01">A quicker alternative to creating a new row while at the end of the table
<li>A quicker alternative to creating a new row while at the end of the table
is to press the <kbd>Tab</kbd> key.</li>
</ul>
</li>
@@ -72,9 +71,9 @@
width="19" height="19">button from the formatting toolbar.</p>
<p>More options are available by pressing the arrow next to it:</p>
<ul>
<li data-list-item-id="e57e54996911ec8dbe17deeb300af481d">Click on a single cell and select Merge cell up/down/right/left to merge
<li>Click on a single cell and select Merge cell up/down/right/left to merge
with an adjacent cell.</li>
<li data-list-item-id="e4510ddae1afe524dab9bb5fa5c9edd88">Select <em>Split cell vertically</em> or <em>horizontally</em>, to split
<li>Select <em>Split cell vertically</em> or <em>horizontally</em>, to split
a cell into multiple cells (can also be used to undo a merge).</li>
</ul>
<h2>Table properties</h2>
@@ -86,16 +85,15 @@
<img src="13_Tables_image.png"
width="19" height="19">button and allows for the following adjustments:</p>
<ul>
<li data-list-item-id="e421d2c037e3e17684b6c875f1d119515">Border (not the border of the cells, but the outer rim of the table),
<li>Border (not the border of the cells, but the outer rim of the table),
which includes the style (single, double), color and width.</li>
<li data-list-item-id="e2e29338b33b5aa3e5b63a914ae412d7d">The background color, with none set by default.</li>
<li data-list-item-id="ee585d003b2f922bbab05dd190070b3e7">The width and height of the table in percentage (must end with <code>%</code>)
<li>The background color, with none set by default.</li>
<li>The width and height of the table in percentage (must end with <code>%</code>)
or pixels (must end with <code>px</code>).</li>
<li data-list-item-id="e23ac3152d03898b653db8d3849f0f5cc">The alignment of the table.
<li>The alignment of the table.
<ul>
<li data-list-item-id="ed90eb8b2432a87fa1fd39fd26e9f2cda">Left or right-aligned, case in which the text will flow next to it.</li>
<li
data-list-item-id="e887b5ce3dc7e47f5e630fbdf0da29862">Centered, case in which text will avoid the table, regardless of the table
<li>Left or right-aligned, case in which the text will flow next to it.</li>
<li>Centered, case in which text will avoid the table, regardless of the table
width.</li>
</ul>
</li>
@@ -113,14 +111,13 @@
on the user's selection).</p>
<p>The following options can be adjusted:</p>
<ul>
<li data-list-item-id="e9e0801ebcbbb763dc569814c922cde20">The border style, color and width (same as table properties), but applying
<li>The border style, color and width (same as table properties), but applying
to the current cell only.</li>
<li data-list-item-id="ec3f1f0bbae91f118bfc86c5bace6edc9">The background color, with none set by default.</li>
<li data-list-item-id="e93abf34e4f6f6a4732cd8a63577567f4">The width and height of the cell in percentage (must end with <code>%</code>)
<li>The background color, with none set by default.</li>
<li>The width and height of the cell in percentage (must end with <code>%</code>)
or pixels (must end with <code>px</code>).</li>
<li data-list-item-id="ed65a0f2554a5130beeb7d478710f7bfe">The padding (the distance of the text compared to the cell's borders).</li>
<li
data-list-item-id="e76d8603c089d10e807576cb2e535a388">The alignment of the text, both horizontally (left, centered, right, justified)
<li>The padding (the distance of the text compared to the cell's borders).</li>
<li>The alignment of the text, both horizontally (left, centered, right, justified)
and vertically (top, middle or bottom).</li>
</ul>
<p>The cell will immediately update to reflect the changes, but the <em>Save</em> button
@@ -134,30 +131,30 @@
<p>By default, tables will come with a predefined gray border.</p>
<p>To adjust the borders, follow these steps:</p>
<ol>
<li data-list-item-id="e04c95cbf1d8480c634f7ba6310d5c66e">Select the table.</li>
<li data-list-item-id="e61b666fe10bca473400bd66f8fd11f36">In the floating panel, select the <em>Table properties</em> option (
<li>Select the table.</li>
<li>In the floating panel, select the <em>Table properties</em> option (
<img
src="14_Tables_image.png" width="21" height="21">).
src="14_Tables_image.png" width="21"
height="21">).
<ol>
<li data-list-item-id="e2ab1b3f540def81d55ec91fe8a313fb3">Look for the <em>Border</em> section at the top of the newly opened panel.</li>
<li
data-list-item-id="eef63fe5e16f8103d07a308b4b9147fbf">This will control the outer borders of the table.</li>
<li data-list-item-id="eca1a3d01633d7d2d32c854bb39e66a53">Select a style for the border. Generally <em>Single</em> is the desirable
<li>Look for the <em>Border</em> section at the top of the newly opened panel.</li>
<li>This will control the outer borders of the table.</li>
<li>Select a style for the border. Generally <em>Single</em> is the desirable
option.</li>
<li data-list-item-id="e7edbe51137f6a7d16d2ca9d7b3f553a2">Select a color for the border.</li>
<li data-list-item-id="e3ad74eae4fddb2fb4c60a1c1efc46950">Select a width for the border, expressed in pixels.&nbsp;</li>
<li>Select a color for the border.</li>
<li>Select a width for the border, expressed in pixels.</li>
</ol>
</li>
<li data-list-item-id="ed56e2883a25aa417c7f4bfddd7a07df6">Select all the cells of the table and then press the <em>Cell properties </em>option
<li>Select all the cells of the table and then press the <em>Cell properties</em> option
(
<img src="9_Tables_image.png" width="21" height="21">).
<img src="9_Tables_image.png" width="21"
height="21">).
<ol>
<li data-list-item-id="e93dc8de15098fd2c0c5674a24205b712">This will control the inner borders of the table, at cell level.</li>
<li
data-list-item-id="ee4f0deb1a42efa29e3b5147f79e92d3b">Note that it's possible to change the borders individually by selecting
<li>This will control the inner borders of the table, at cell level.</li>
<li>Note that it's possible to change the borders individually by selecting
one or more cells, case in which it will only change the borders that intersect
these cells.</li>
<li data-list-item-id="e3af86e5d6096d8afd7413012a9ecb6c6">Repeat the same steps as from step (2).</li>
<li>Repeat the same steps as from step (2).</li>
</ol>
</li>
</ol>
@@ -166,19 +163,18 @@
layouts (columns, grids) of text or <a href="#root/_help_mT0HEkOsz6i1">images</a> without
the distraction of their border:</p>
<ol>
<li data-list-item-id="e078bac0af18c7fe8f4ac2272a312e49d">First insert a table with the desired number of columns and rows.</li>
<li
data-list-item-id="ee25683e809fd85e65db655dea9efece5">Select the entire table.</li>
<li data-list-item-id="e66342ca801e71a9aba89fc217fcc66a5">In <em>Table properties</em>, set:
<li>First insert a table with the desired number of columns and rows.</li>
<li>Select the entire table.</li>
<li>In <em>Table properties</em>, set:
<ol>
<li data-list-item-id="edef2e2e5a6a8ac73e36101f4429863d8"><em>Style</em> to <em>Single</em>
<li><em>Style</em> to <em>Single</em>
</li>
<li data-list-item-id="e6aaccfd6440d2999079364982c0b33fc"><em>Color</em> to <code>transparent</code>
<li><em>Color</em> to <code>transparent</code>
</li>
<li data-list-item-id="e13494edb598fd972373a368e79540ea1">Width to <code>1px</code>.</li>
<li>Width to <code>1px</code>.</li>
</ol>
</li>
<li data-list-item-id="e15221332e526341aefb8b87e715c0e32">In Cell Properties, set the same as on the previous step.</li>
<li>In Cell Properties, set the same as on the previous step.</li>
</ol>
<h2>Markdown import/export</h2>
<p>Simple tables are exported in GitHub-flavored Markdown format (e.g. 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

@@ -8,7 +8,7 @@
get parentWidget() { return "left-pane"; }
doRender() {
this.$widget = $("");
this.$widget = $("&lt;div id='my-widget'&gt;");
return this.$widget;
}
}
@@ -22,13 +22,13 @@ module.exports = new MyWidget();</code></pre>
the <a href="#root/_help_BFs8mudNFgCS">note</a>.</li>
<li>Restart Trilium or reload the window.</li>
</ol>
<p>To verify that the widget is working, open the developer tools (<code>Cmd</code> + <code>Shift</code> + <code>I</code>)
<p>To verify that the widget is working, open the developer tools (<kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>I</kbd>)
and run <code>document.querySelector("#my-widget")</code>. If the element
is found, the widget is functioning correctly. If <code>undefined</code> is
returned, double-check that the <a href="#root/_help_BFs8mudNFgCS">note</a> has
the <code>#widget</code> <a href="#root/_help_zEY4DaJG4YT5">attribute</a>.</p>
<h3>Step 2: Adding an UI Element</h3>
<p>Next, let's improve the widget by adding a button to it.</p><pre><code class="language-text-x-trilium-auto">const template = ``;
<p>Next, let's improve the widget by adding a button to it.</p><pre><code class="language-text-x-trilium-auto">const template = `&lt;div id="my-widget"&gt;&lt;button&gt;Click Me!&lt;/button&gt;&lt;/div&gt;`;
class MyWidget extends api.BasicWidget {
get position() {return 1;}
@@ -47,7 +47,7 @@ module.exports = new MyWidget();</code></pre>
<p>To make the button more visually appealing and position it correctly,
we'll apply some custom styling. Trilium includes <a href="https://boxicons.com">Box Icons</a>,
which we'll use to replace the button text with an icon. For example the <code>bx bxs-magic-wand</code> icon.</p>
<p>Here's the updated template:</p><pre><code class="language-text-x-trilium-auto">const template = ``;</code></pre>
<p>Here's the updated template:</p><pre><code class="language-text-x-trilium-auto">const template = `&lt;div id="my-widget"&gt;&lt;button class="tree-floating-button bx bxs-magic-wand tree-settings-button"&gt;&lt;/button&gt;&lt;/div&gt;`;</code></pre>
<p>Next, we'll adjust the button's position using CSS:</p><pre><code class="language-text-x-trilium-auto">class MyWidget extends api.BasicWidget {
get position() { return 1; }
get parentWidget() { return "left-pane"; }
@@ -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>

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