diff --git a/.github/actions/build-server/action.yml b/.github/actions/build-server/action.yml
index faa3c0752..b92b3875f 100644
--- a/.github/actions/build-server/action.yml
+++ b/.github/actions/build-server/action.yml
@@ -12,7 +12,7 @@ runs:
- name: Set up node & dependencies
uses: actions/setup-node@v6
with:
- node-version: 22
+ node-version: 24
cache: "pnpm"
- name: Install dependencies
shell: bash
diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml
index 62e04d48d..5e8fb1301 100644
--- a/.github/workflows/deploy-docs.yml
+++ b/.github/workflows/deploy-docs.yml
@@ -1,6 +1,4 @@
-# GitHub Actions workflow for deploying MkDocs documentation to Cloudflare Pages
-# This workflow builds and deploys your MkDocs site when changes are pushed to main
-name: Deploy MkDocs Documentation
+name: Deploy Documentation
on:
# Trigger on push to main branch
@@ -11,11 +9,9 @@ on:
# Only run when docs files change
paths:
- 'docs/**'
- - 'README.md' # README is synced to docs/index.md
- - 'mkdocs.yml'
- - 'requirements-docs.txt'
- - '.github/workflows/deploy-docs.yml'
- - 'scripts/fix-mkdocs-structure.ts'
+ - 'apps/edit-docs/**'
+ - 'apps/build-docs/**'
+ - 'packages/share-theme/**'
# Allow manual triggering from Actions tab
workflow_dispatch:
@@ -27,15 +23,13 @@ on:
- master
paths:
- 'docs/**'
- - 'README.md' # README is synced to docs/index.md
- - 'mkdocs.yml'
- - 'requirements-docs.txt'
- - '.github/workflows/deploy-docs.yml'
- - 'scripts/fix-mkdocs-structure.ts'
+ - 'apps/edit-docs/**'
+ - 'apps/build-docs/**'
+ - 'packages/share-theme/**'
jobs:
build-and-deploy:
- name: Build and Deploy MkDocs
+ name: Build and Deploy Documentation
runs-on: ubuntu-latest
timeout-minutes: 10
@@ -49,72 +43,27 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v5
- with:
- fetch-depth: 0 # Fetch all history for git info and mkdocs-git-revision-date plugin
- - name: Setup Python
- uses: actions/setup-python@v6
- with:
- python-version: '3.14'
- cache: 'pip'
- cache-dependency-path: 'requirements-docs.txt'
-
- - name: Install MkDocs and Dependencies
- run: |
- pip install --upgrade pip
- pip install -r requirements-docs.txt
- env:
- PIP_DISABLE_PIP_VERSION_CHECK: 1
-
- # Setup pnpm before fixing docs structure
- name: Setup pnpm
uses: pnpm/action-setup@v4
- # Setup Node.js with pnpm
- name: Setup Node.js
uses: actions/setup-node@v6
with:
- node-version: '22'
+ node-version: '24'
cache: 'pnpm'
- # Install Node.js dependencies for the TypeScript script
- name: Install Dependencies
- run: |
- pnpm install --frozen-lockfile
+ run: pnpm install --frozen-lockfile
- - name: Fix Documentation Structure
- run: |
- # Fix duplicate navigation entries by moving overview pages to index.md
- pnpm run chore:fix-mkdocs-structure
-
- - name: Build MkDocs Site
- run: |
- # Build with strict mode but allow expected warnings
- mkdocs build --verbose || {
- EXIT_CODE=$?
- # Check if the only issue is expected warnings
- if mkdocs build 2>&1 | grep -E "WARNING.*(README|not found)" && \
- [ $(mkdocs build 2>&1 | grep -c "ERROR") -eq 0 ]; then
- echo "✅ Build succeeded with expected warnings"
- mkdocs build --verbose
- else
- echo "❌ Build failed with unexpected errors"
- exit $EXIT_CODE
- fi
- }
-
- - name: Fix HTML Links
- run: |
- # Remove .md extensions from links in generated HTML
- pnpm tsx ./scripts/fix-html-links.ts site
+ - name: Trigger build of documentation
+ run: pnpm docs:build
- name: Validate Built Site
run: |
- # Basic validation that important files exist
test -f site/index.html || (echo "ERROR: site/index.html not found" && exit 1)
- test -f site/sitemap.xml || (echo "ERROR: site/sitemap.xml not found" && exit 1)
- test -d site/assets || (echo "ERROR: site/assets directory not found" && exit 1)
- echo "✅ Site validation passed"
+ 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
diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml
index ec4aeda0e..f9174fb42 100644
--- a/.github/workflows/dev.yml
+++ b/.github/workflows/dev.yml
@@ -30,7 +30,7 @@ jobs:
- name: Set up node & dependencies
uses: actions/setup-node@v6
with:
- node-version: 22
+ node-version: 24
cache: "pnpm"
- run: pnpm install --frozen-lockfile
diff --git a/.github/workflows/main-docker.yml b/.github/workflows/main-docker.yml
index 827320146..fab20d242 100644
--- a/.github/workflows/main-docker.yml
+++ b/.github/workflows/main-docker.yml
@@ -46,7 +46,7 @@ jobs:
- name: Set up node & dependencies
uses: actions/setup-node@v6
with:
- node-version: 22
+ node-version: 24
cache: "pnpm"
- name: Install npm dependencies
@@ -116,10 +116,10 @@ jobs:
- dockerfile: Dockerfile
platform: linux/arm64
image: ubuntu-24.04-arm
- - dockerfile: Dockerfile
+ - dockerfile: Dockerfile.legacy
platform: linux/arm/v7
image: ubuntu-24.04-arm
- - dockerfile: Dockerfile
+ - dockerfile: Dockerfile.legacy
platform: linux/arm/v8
image: ubuntu-24.04-arm
runs-on: ${{ matrix.image }}
@@ -146,7 +146,7 @@ jobs:
- name: Set up node & dependencies
uses: actions/setup-node@v6
with:
- node-version: 22
+ node-version: 24
cache: 'pnpm'
- name: Install dependencies
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index ac15f9914..ddce68d42 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -52,7 +52,7 @@ jobs:
- name: Set up node & dependencies
uses: actions/setup-node@v6
with:
- node-version: 22
+ node-version: 24
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml
index e2a2757f5..a33d24283 100644
--- a/.github/workflows/playwright.yml
+++ b/.github/workflows/playwright.yml
@@ -24,7 +24,7 @@ jobs:
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
with:
- node-version: 22
+ node-version: 24
cache: 'pnpm'
- name: Install dependencies
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 1bd2d89ba..3d48cb80d 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -50,7 +50,7 @@ jobs:
- name: Set up node & dependencies
uses: actions/setup-node@v6
with:
- node-version: 22
+ node-version: 24
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml
index fe94c987a..7a87cc192 100644
--- a/.github/workflows/website.yml
+++ b/.github/workflows/website.yml
@@ -30,7 +30,7 @@ jobs:
- name: Set up node & dependencies
uses: actions/setup-node@v6
with:
- node-version: 22
+ node-version: 24
cache: "pnpm"
- name: Install dependencies
diff --git a/.nvmrc b/.nvmrc
index f5b3ef39f..40115e966 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-22.21.0
\ No newline at end of file
+24.11.0
\ No newline at end of file
diff --git a/_regroup/package.json b/_regroup/package.json
index 1937327e0..ce0e70d45 100644
--- a/_regroup/package.json
+++ b/_regroup/package.json
@@ -37,20 +37,18 @@
"devDependencies": {
"@playwright/test": "1.56.1",
"@stylistic/eslint-plugin": "5.5.0",
- "@types/express": "5.0.4",
- "@types/node": "22.18.12",
+ "@types/express": "5.0.5",
+ "@types/node": "24.10.0",
"@types/yargs": "17.0.34",
"@vitest/coverage-v8": "3.2.4",
- "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"
diff --git a/_regroup/typedoc.json b/_regroup/typedoc.json
deleted file mode 100644
index 30771621c..000000000
--- a/_regroup/typedoc.json
+++ /dev/null
@@ -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"
- }
- ]
-}
diff --git a/apps/build-docs/package.json b/apps/build-docs/package.json
new file mode 100644
index 000000000..00196de82
--- /dev/null
+++ b/apps/build-docs/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "build-docs",
+ "version": "1.0.0",
+ "description": "",
+ "main": "src/main.ts",
+ "scripts": {
+ "start": "tsx ."
+ },
+ "keywords": [],
+ "author": "Elian Doran ",
+ "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"
+ }
+}
diff --git a/apps/build-docs/src/backend_script_entrypoint.ts b/apps/build-docs/src/backend_script_entrypoint.ts
new file mode 100644
index 000000000..bc9087c0c
--- /dev/null
+++ b/apps/build-docs/src/backend_script_entrypoint.ts
@@ -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, {});
diff --git a/apps/build-docs/src/build-docs.ts b/apps/build-docs/src/build-docs.ts
new file mode 100644
index 000000000..5d1a0cdd6
--- /dev/null
+++ b/apps/build-docs/src/build-docs.ts
@@ -0,0 +1,147 @@
+process.env.TRILIUM_INTEGRATION_TEST = "memory-no-store";
+process.env.TRILIUM_RESOURCE_DIR = "../server/src";
+process.env.NODE_ENV = "development";
+
+import cls from "@triliumnext/server/src/services/cls.js";
+import { dirname, join, resolve } from "path";
+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 importAndExportDocs(sourcePath: string, outputSubDir: string) {
+ const note = await importData(sourcePath);
+
+ // 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];
+ const taskContext = new (await import("@triliumnext/server/src/services/task_context.js")).default(
+ "no-progress-reporting",
+ "export",
+ null
+ );
+ const fileOutputStream = fsExtra.createWriteStream(zipFilePath);
+ await exportToZip(taskContext, branch, "share", fileOutputStream);
+ await waitForStreamToFinish(fileOutputStream);
+
+ // 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 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("../../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("../../server/src/becca/becca.js")).default;
+
+ const rootNote = becca.getRoot();
+ if (!rootNote) {
+ throw new Error("Missing root note for import.");
+ }
+ return await importService.importZip(context, buffer, rootNote, {
+ preserveIds: true
+ });
+}
+
+async function createImportZip(path: string) {
+ const inputFile = "input.zip";
+ const archive = archiver("zip", {
+ zlib: { level: 0 }
+ });
+
+ console.log("Archive path is ", resolve(path))
+ archive.directory(path, "/");
+
+ const outputStream = fsExtra.createWriteStream(inputFile);
+ archive.pipe(outputStream);
+ archive.finalize();
+ await waitForStreamToFinish(outputStream);
+
+ try {
+ return await fsExtra.readFile(inputFile);
+ } finally {
+ await fsExtra.rm(inputFile);
+ }
+}
+
+function waitForStreamToFinish(stream: WriteStream) {
+ return new Promise((res, rej) => {
+ stream.on("finish", () => res());
+ stream.on("error", (err) => rej(err));
+ });
+}
+
+export async function extractZip(zipFilePath: string, outputPath: string, ignoredFiles?: Set) {
+ const { readZipFile, readContent } = (await import("@triliumnext/server/src/services/import/zip.js"));
+ await readZipFile(await fs.readFile(zipFilePath), async (zip, entry) => {
+ // We ignore directories since they can appear out of order anyway.
+ if (!entry.fileName.endsWith("/") && !ignoredFiles?.has(entry.fileName)) {
+ const destPath = join(outputPath, entry.fileName);
+ const fileContent = await readContent(zip, entry);
+
+ await fsExtra.mkdirs(dirname(destPath));
+ await fs.writeFile(destPath, fileContent);
+ }
+
+ zip.readEntry();
+ });
+}
+
+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);
+ });
+ });
+}
diff --git a/apps/build-docs/src/context.ts b/apps/build-docs/src/context.ts
new file mode 100644
index 000000000..ab2289e50
--- /dev/null
+++ b/apps/build-docs/src/context.ts
@@ -0,0 +1,4 @@
+export default interface BuildContext {
+ gitRootDir: string;
+ baseDir: string;
+}
diff --git a/apps/build-docs/src/frontend_script_entrypoint.ts b/apps/build-docs/src/frontend_script_entrypoint.ts
new file mode 100644
index 000000000..768774eca
--- /dev/null
+++ b/apps/build-docs/src/frontend_script_entrypoint.ts
@@ -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();
diff --git a/apps/build-docs/src/index.html b/apps/build-docs/src/index.html
new file mode 100644
index 000000000..47a0bfb34
--- /dev/null
+++ b/apps/build-docs/src/index.html
@@ -0,0 +1,10 @@
+
+
+
+
+ Redirecting...
+
+
+ If you are not redirected automatically, click here .
+
+
\ No newline at end of file
diff --git a/apps/build-docs/src/main.ts b/apps/build-docs/src/main.ts
new file mode 100644
index 000000000..d94ada167
--- /dev/null
+++ b/apps/build-docs/src/main.ts
@@ -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();
diff --git a/apps/build-docs/src/script-api.ts b/apps/build-docs/src/script-api.ts
new file mode 100644
index 000000000..8473ae3a0
--- /dev/null
+++ b/apps/build-docs/src/script-api.ts
@@ -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"
+ });
+ }
+}
diff --git a/apps/build-docs/src/swagger.ts b/apps/build-docs/src/swagger.ts
new file mode 100644
index 000000000..b3677aeeb
--- /dev/null
+++ b/apps/build-docs/src/swagger.ts
@@ -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" });
+ }
+}
diff --git a/apps/build-docs/tsconfig.app.json b/apps/build-docs/tsconfig.app.json
new file mode 100644
index 000000000..b9e17115a
--- /dev/null
+++ b/apps/build-docs/tsconfig.app.json
@@ -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"
+ }
+ ]
+}
diff --git a/apps/build-docs/tsconfig.json b/apps/build-docs/tsconfig.json
new file mode 100644
index 000000000..858921cfb
--- /dev/null
+++ b/apps/build-docs/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "include": [],
+ "references": [
+ {
+ "path": "../server"
+ },
+ {
+ "path": "../client"
+ },
+ {
+ "path": "./tsconfig.app.json"
+ }
+ ]
+}
diff --git a/apps/build-docs/typedoc.backend.json b/apps/build-docs/typedoc.backend.json
new file mode 100644
index 000000000..1781774c6
--- /dev/null
+++ b/apps/build-docs/typedoc.backend.json
@@ -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"
+ ]
+}
diff --git a/apps/build-docs/typedoc.frontend.json b/apps/build-docs/typedoc.frontend.json
new file mode 100644
index 000000000..f07d20dc7
--- /dev/null
+++ b/apps/build-docs/typedoc.frontend.json
@@ -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"
+ ]
+}
diff --git a/apps/client/package.json b/apps/client/package.json
index 61738b8bb..d2ca22049 100644
--- a/apps/client/package.json
+++ b/apps/client/package.json
@@ -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",
@@ -54,12 +54,12 @@
"leaflet-gpx": "2.2.0",
"mark.js": "8.11.1",
"marked": "16.4.1",
- "mermaid": "11.12.0",
- "mind-elixir": "5.3.4",
+ "mermaid": "11.12.1",
+ "mind-elixir": "5.3.5",
"normalize.css": "8.0.1",
"panzoom": "9.4.3",
"preact": "10.27.2",
- "react-i18next": "16.2.0",
+ "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"
}
diff --git a/apps/client/src/components/app_context.ts b/apps/client/src/components/app_context.ts
index 322e72704..ecdd589be 100644
--- a/apps/client/src/components/app_context.ts
+++ b/apps/client/src/components/app_context.ts
@@ -33,7 +33,7 @@ import { SqlExecuteResults } from "@triliumnext/commons";
import { AddLinkOpts } from "../widgets/dialogs/add_link.jsx";
import { IncludeNoteOpts } from "../widgets/dialogs/include_note.jsx";
import { ReactWrappedWidget } from "../widgets/basic_widget.js";
-import { TypeWidget } from "../widgets/note_types.jsx";
+import type { MarkdownImportOpts } from "../widgets/dialogs/markdown_import.jsx";
interface Layout {
getRootWidget: (appContext: AppContext) => RootContainer;
@@ -219,12 +219,12 @@ export type CommandMappings = {
/** Works only in the electron context menu. */
replaceMisspelling: CommandData;
- importMarkdownInline: CommandData;
showPasswordNotSet: CommandData;
showProtectedSessionPasswordDialog: CommandData;
showUploadAttachmentsDialog: CommandData & { noteId: string };
showIncludeNoteDialog: CommandData & IncludeNoteOpts;
showAddLinkDialog: CommandData & AddLinkOpts;
+ showPasteMarkdownDialog: CommandData & MarkdownImportOpts;
closeProtectedSessionPasswordDialog: CommandData;
copyImageReferenceToClipboard: CommandData;
copyImageToClipboard: CommandData;
@@ -271,6 +271,7 @@ export type CommandMappings = {
closeThisNoteSplit: CommandData;
moveThisNoteSplit: CommandData & { isMovingLeft: boolean };
jumpToNote: CommandData;
+ openTodayNote: CommandData;
commandPalette: CommandData;
// Keyboard shortcuts
diff --git a/apps/client/src/components/entrypoints.ts b/apps/client/src/components/entrypoints.ts
index 91895d77f..57d359abe 100644
--- a/apps/client/src/components/entrypoints.ts
+++ b/apps/client/src/components/entrypoints.ts
@@ -159,6 +159,16 @@ export default class Entrypoints extends Component {
this.openInWindowCommand({ notePath: "", hoistedNoteId: "root" });
}
+ async openTodayNoteCommand() {
+ const todayNote = await dateNoteService.getTodayNote();
+ if (!todayNote) {
+ console.warn("Missing today note.");
+ return;
+ }
+
+ await appContext.tabManager.openInSameTab(todayNote.noteId);
+ }
+
async runActiveNoteCommand() {
const noteContext = appContext.tabManager.getActiveContext();
if (!noteContext) {
diff --git a/apps/client/src/entities/fnote.ts b/apps/client/src/entities/fnote.ts
index bcb6c408e..6d0a15506 100644
--- a/apps/client/src/entities/fnote.ts
+++ b/apps/client/src/entities/fnote.ts
@@ -417,7 +417,7 @@ export default class FNote {
return notePaths;
}
- getSortedNotePathRecords(hoistedNoteId = "root"): NotePathRecord[] {
+ getSortedNotePathRecords(hoistedNoteId = "root", activeNotePath: string | null = null): NotePathRecord[] {
const isHoistedRoot = hoistedNoteId === "root";
const notePaths: NotePathRecord[] = this.getAllNotePaths().map((path) => ({
@@ -428,7 +428,23 @@ export default class FNote {
isHidden: path.includes("_hidden")
}));
+ // Calculate the length of the prefix match between two arrays
+ const prefixMatchLength = (path: string[], target: string[]) => {
+ const diffIndex = path.findIndex((seg, i) => seg !== target[i]);
+ return diffIndex === -1 ? Math.min(path.length, target.length) : diffIndex;
+ };
+
notePaths.sort((a, b) => {
+ if (activeNotePath) {
+ const activeSegments = activeNotePath.split('/');
+ const aOverlap = prefixMatchLength(a.notePath, activeSegments);
+ const bOverlap = prefixMatchLength(b.notePath, activeSegments);
+ // Paths with more matching prefix segments are prioritized
+ // when the match count is equal, other criteria are used for sorting
+ if (bOverlap !== aOverlap) {
+ return bOverlap - aOverlap;
+ }
+ }
if (a.isInHoistedSubTree !== b.isInHoistedSubTree) {
return a.isInHoistedSubTree ? -1 : 1;
} else if (a.isArchived !== b.isArchived) {
@@ -449,10 +465,11 @@ export default class FNote {
* Returns the note path considered to be the "best"
*
* @param {string} [hoistedNoteId='root']
+ * @param {string|null} [activeNotePath=null]
* @return {string[]} array of noteIds constituting the particular note path
*/
- getBestNotePath(hoistedNoteId = "root") {
- return this.getSortedNotePathRecords(hoistedNoteId)[0]?.notePath;
+ getBestNotePath(hoistedNoteId = "root", activeNotePath: string | null = null) {
+ return this.getSortedNotePathRecords(hoistedNoteId, activeNotePath)[0]?.notePath;
}
/**
diff --git a/apps/client/src/menus/tree_context_menu.ts b/apps/client/src/menus/tree_context_menu.ts
index 6504b49eb..7384573d8 100644
--- a/apps/client/src/menus/tree_context_menu.ts
+++ b/apps/client/src/menus/tree_context_menu.ts
@@ -137,7 +137,7 @@ export default class TreeContextMenu implements SelectMenuItemEventListener {
+ if (img.complete) return Promise.resolve();
+ return new Promise(resolve => {
+ img.addEventListener("load", () => resolve(), { once: true });
+ img.addEventListener("error", () => resolve(), { once: true });
+ });
+ })
+ );
}
load().then(() => requestAnimationFrame(onReady))
diff --git a/apps/client/src/services/frontend_script_entrypoint.ts b/apps/client/src/services/frontend_script_entrypoint.ts
deleted file mode 100644
index 75a27d204..000000000
--- a/apps/client/src/services/frontend_script_entrypoint.ts
+++ /dev/null
@@ -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();
diff --git a/apps/client/src/services/glob.ts b/apps/client/src/services/glob.ts
index 48d0d29a7..44ce64309 100644
--- a/apps/client/src/services/glob.ts
+++ b/apps/client/src/services/glob.ts
@@ -20,9 +20,6 @@ function setupGlobs() {
window.glob.froca = froca;
window.glob.treeCache = froca; // compatibility for CKEditor builds for a while
- // for CKEditor integration (button on block toolbar)
- window.glob.importMarkdownInline = async () => appContext.triggerCommand("importMarkdownInline");
-
window.onerror = function (msg, url, lineNo, columnNo, error) {
const string = String(msg).toLowerCase();
diff --git a/apps/client/src/services/in_app_help.ts b/apps/client/src/services/in_app_help.ts
index a0b118e5c..2f805783a 100644
--- a/apps/client/src/services/in_app_help.ts
+++ b/apps/client/src/services/in_app_help.ts
@@ -10,7 +10,7 @@ export const byNoteType: Record, string | null> = {
file: null,
image: null,
launcher: null,
- mermaid: null,
+ mermaid: "s1aBHPd79XYj",
mindMap: null,
noteMap: null,
relationMap: null,
diff --git a/apps/client/src/services/shortcuts.spec.ts b/apps/client/src/services/shortcuts.spec.ts
index 87f8ae489..b9576025f 100644
--- a/apps/client/src/services/shortcuts.spec.ts
+++ b/apps/client/src/services/shortcuts.spec.ts
@@ -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();
}
diff --git a/apps/client/src/services/shortcuts.ts b/apps/client/src/services/shortcuts.ts
index 363767b71..2c6345fcb 100644
--- a/apps/client/src/services/shortcuts.ts
+++ b/apps/client/src/services/shortcuts.ts
@@ -46,6 +46,7 @@ for (let i = 1; i <= 19; i++) {
const KEYCODES_WITH_NO_MODIFIER = new Set([
"Delete",
"Enter",
+ "NumpadEnter",
...functionKeyCodes
]);
diff --git a/apps/client/src/services/tree.ts b/apps/client/src/services/tree.ts
index fc54c3c75..ec5bc0191 100644
--- a/apps/client/src/services/tree.ts
+++ b/apps/client/src/services/tree.ts
@@ -26,21 +26,12 @@ async function resolveNotePathToSegments(notePath: string, hoistedNoteId = "root
}
const path = notePath.split("/").reverse();
-
- if (!path.includes("root")) {
- path.push("root");
- }
-
const effectivePathSegments: string[] = [];
let childNoteId: string | null = null;
let i = 0;
- while (true) {
- if (i >= path.length) {
- break;
- }
-
- const parentNoteId = path[i++];
+ for (let i = 0; i < path.length; i++) {
+ const parentNoteId = path[i];
if (childNoteId !== null) {
const child = await froca.getNote(childNoteId, !logErrors);
@@ -65,7 +56,7 @@ async function resolveNotePathToSegments(notePath: string, hoistedNoteId = "root
return null;
}
- if (!parents.some((p) => p.noteId === parentNoteId)) {
+ if (!parents.some(p => p.noteId === parentNoteId) || (i === path.length - 1 && parentNoteId !== 'root')) {
if (logErrors) {
const parent = froca.getNoteFromCache(parentNoteId);
@@ -77,7 +68,8 @@ async function resolveNotePathToSegments(notePath: string, hoistedNoteId = "root
);
}
- const bestNotePath = child.getBestNotePath(hoistedNoteId);
+ const activeNotePath = appContext.tabManager.getActiveContextNotePath();
+ const bestNotePath = child.getBestNotePath(hoistedNoteId, activeNotePath);
if (bestNotePath) {
const pathToRoot = bestNotePath.reverse().slice(1);
@@ -108,7 +100,9 @@ async function resolveNotePathToSegments(notePath: string, hoistedNoteId = "root
if (!note) {
throw new Error(`Unable to find note: ${notePath}.`);
}
- const bestNotePath = note.getBestNotePath(hoistedNoteId);
+
+ const activeNotePath = appContext.tabManager.getActiveContextNotePath();
+ const bestNotePath = note.getBestNotePath(hoistedNoteId, activeNotePath);
if (!bestNotePath) {
throw new Error(`Did not find any path segments for '${note.toString()}', hoisted note '${hoistedNoteId}'`);
diff --git a/apps/client/src/services/utils.ts b/apps/client/src/services/utils.ts
index 2f33a4011..be66f642d 100644
--- a/apps/client/src/services/utils.ts
+++ b/apps/client/src/services/utils.ts
@@ -11,7 +11,11 @@ export function reloadFrontendApp(reason?: string) {
logInfo(`Frontend app reload: ${reason}`);
}
- window.location.reload();
+ if (isElectron()) {
+ dynamicRequire("@electron/remote").BrowserWindow.getFocusedWindow()?.reload();
+ } else {
+ window.location.reload();
+ }
}
export function restartDesktopApp() {
diff --git a/apps/client/src/share.ts b/apps/client/src/share.ts
deleted file mode 100644
index b438f0c0c..000000000
--- a/apps/client/src/share.ts
+++ /dev/null
@@ -1,84 +0,0 @@
-import "normalize.css";
-import "boxicons/css/boxicons.min.css";
-import "@triliumnext/ckeditor5/src/theme/ck-content.css";
-import "@triliumnext/share-theme/styles/index.css";
-import "@triliumnext/share-theme/scripts/index.js";
-
-async function ensureJQuery() {
- const $ = (await import("jquery")).default;
- (window as any).$ = $;
-}
-
-async function applyMath() {
- const anyMathBlock = document.querySelector("#content .math-tex");
- if (!anyMathBlock) {
- return;
- }
-
- const renderMathInElement = (await import("./services/math.js")).renderMathInElement;
- renderMathInElement(document.getElementById("content"));
-}
-
-async function formatCodeBlocks() {
- const anyCodeBlock = document.querySelector("#content pre");
- if (!anyCodeBlock) {
- return;
- }
- await ensureJQuery();
- const { formatCodeBlocks } = await import("./services/syntax_highlight.js");
- await formatCodeBlocks($("#content"));
-}
-
-async function setupTextNote() {
- formatCodeBlocks();
- applyMath();
-
- const setupMermaid = (await import("./share/mermaid.js")).default;
- setupMermaid();
-}
-
-/**
- * Fetch note with given ID from backend
- *
- * @param noteId of the given note to be fetched. If false, fetches current note.
- */
-async function fetchNote(noteId: string | null = null) {
- if (!noteId) {
- noteId = document.body.getAttribute("data-note-id");
- }
-
- const resp = await fetch(`api/notes/${noteId}`);
-
- return await resp.json();
-}
-
-document.addEventListener(
- "DOMContentLoaded",
- () => {
- const noteType = determineNoteType();
-
- if (noteType === "text") {
- setupTextNote();
- }
-
- const toggleMenuButton = document.getElementById("toggleMenuButton");
- const layout = document.getElementById("layout");
-
- if (toggleMenuButton && layout) {
- toggleMenuButton.addEventListener("click", () => layout.classList.toggle("showMenu"));
- }
- },
- false
-);
-
-function determineNoteType() {
- const bodyClass = document.body.className;
- const match = bodyClass.match(/type-([^\s]+)/);
- return match ? match[1] : null;
-}
-
-// workaround to prevent webpack from removing "fetchNote" as dead code:
-// add fetchNote as property to the window object
-Object.defineProperty(window, "fetchNote", {
- value: fetchNote
-});
diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css
index 4916f18b9..6e6cea21a 100644
--- a/apps/client/src/stylesheets/style.css
+++ b/apps/client/src/stylesheets/style.css
@@ -2034,9 +2034,9 @@ body.zen #right-pane,
body.zen #mobile-sidebar-wrapper,
body.zen .tab-row-container,
body.zen .tab-row-widget,
-body.zen .ribbon-container:not(:has(.classic-toolbar-widget.visible)),
-body.zen .ribbon-container:has(.classic-toolbar-widget.visible) .ribbon-top-row,
-body.zen .ribbon-container .ribbon-body:not(:has(.classic-toolbar-widget.visible)),
+body.zen .ribbon-container:not(:has(.classic-toolbar-widget)),
+body.zen .ribbon-container:has(.classic-toolbar-widget) .ribbon-top-row,
+body.zen .ribbon-container .ribbon-body:not(:has(.classic-toolbar-widget)),
body.zen .note-icon-widget,
body.zen .title-row .icon-action,
body.zen .floating-buttons-children > *:not(.bx-edit-alt),
diff --git a/apps/client/src/translations/ar/translation.json b/apps/client/src/translations/ar/translation.json
index b04472d7c..dffb4f9d4 100644
--- a/apps/client/src/translations/ar/translation.json
+++ b/apps/client/src/translations/ar/translation.json
@@ -716,7 +716,6 @@
"backup_database_now": "نسخ اختياطي لقاعدة البيانات الان"
},
"etapi": {
- "wiki": "ويكي",
"created": "تم الأنشاء",
"actions": "أجراءات",
"title": "ETAPI",
diff --git a/apps/client/src/translations/cn/translation.json b/apps/client/src/translations/cn/translation.json
index b635b557f..b36aba22b 100644
--- a/apps/client/src/translations/cn/translation.json
+++ b/apps/client/src/translations/cn/translation.json
@@ -51,7 +51,7 @@
"bulk_actions_executed": "批量操作已成功执行。",
"none_yet": "暂无操作 ... 通过点击上方的可用操作添加一个操作。",
"labels": "标签",
- "relations": "关联关系",
+ "relations": "关系",
"notes": "笔记",
"other": "其它"
},
@@ -104,7 +104,8 @@
"export_status": "导出状态",
"export_in_progress": "导出进行中:{{progressCount}}",
"export_finished_successfully": "导出成功完成。",
- "format_pdf": "PDF - 用于打印或共享目的。"
+ "format_pdf": "PDF - 用于打印或共享目的。",
+ "share-format": "HTML 网页发布——采用与共享笔记相同的主题,但可发布为静态网站。"
},
"help": {
"noteNavigation": "笔记导航",
@@ -184,7 +185,8 @@
},
"import-status": "导入状态",
"in-progress": "导入进行中:{{progress}}",
- "successful": "导入成功完成。"
+ "successful": "导入成功完成。",
+ "importZipRecommendation": "导入 ZIP 文件时,笔记层级将反映压缩文件内的子目录结构。"
},
"include_note": {
"dialog_title": "包含笔记",
@@ -259,7 +261,6 @@
"delete_all_revisions": "删除此笔记的所有修订版本",
"delete_all_button": "删除所有修订版本",
"help_title": "关于笔记修订版本的帮助",
- "revision_last_edited": "此修订版本上次编辑于 {{date}}",
"confirm_delete_all": "您是否要删除此笔记的所有修订版本?",
"no_revisions": "此笔记暂无修订版本...",
"restore_button": "恢复",
@@ -1288,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": "目前还没有令牌。点击上面的按钮创建一个。",
@@ -1558,7 +1555,9 @@
"window-on-top": "保持此窗口置顶"
},
"note_detail": {
- "could_not_find_typewidget": "找不到类型为 '{{type}}' 的 typeWidget"
+ "could_not_find_typewidget": "找不到类型为 '{{type}}' 的 typeWidget",
+ "printing": "正在打印…",
+ "printing_pdf": "正在导出为PDF…"
},
"note_title": {
"placeholder": "请输入笔记标题..."
diff --git a/apps/client/src/translations/de/translation.json b/apps/client/src/translations/de/translation.json
index e34982c6a..4f7e9801f 100644
--- a/apps/client/src/translations/de/translation.json
+++ b/apps/client/src/translations/de/translation.json
@@ -4,7 +4,7 @@
"homepage": "Startseite:",
"app_version": "App-Version:",
"db_version": "DB-Version:",
- "sync_version": "Synch-version:",
+ "sync_version": "Sync-Version:",
"build_date": "Build-Datum:",
"build_revision": "Build-Revision:",
"data_directory": "Datenverzeichnis:"
@@ -104,7 +104,8 @@
"export_status": "Exportstatus",
"export_in_progress": "Export läuft: {{progressCount}}",
"export_finished_successfully": "Der Export wurde erfolgreich abgeschlossen.",
- "format_pdf": "PDF - für Ausdrucke oder Teilen."
+ "format_pdf": "PDF - für Ausdrucke oder Teilen.",
+ "share-format": "HTML für die Web-Veröffentlichung – verwendet dasselbe Theme wie bei freigegebenen Notizen, kann jedoch als statische Website veröffentlicht werden."
},
"help": {
"noteNavigation": "Notiz Navigation",
@@ -260,7 +261,6 @@
"delete_all_revisions": "Lösche alle Revisionen dieser Notiz",
"delete_all_button": "Alle Revisionen löschen",
"help_title": "Hilfe zu Notizrevisionen",
- "revision_last_edited": "Diese Revision wurde zuletzt am {{date}} bearbeitet",
"confirm_delete_all": "Möchtest du alle Revisionen dieser Notiz löschen?",
"no_revisions": "Für diese Notiz gibt es noch keine Revisionen...",
"confirm_restore": "Möchtest du diese Revision wiederherstellen? Dadurch werden der aktuelle Titel und Inhalt der Notiz mit dieser Revision überschrieben.",
@@ -991,7 +991,7 @@
"enter_password_instruction": "Um die geschützte Notiz anzuzeigen, musst du dein Passwort eingeben:",
"start_session_button": "Starte eine geschützte Sitzung",
"started": "Geschützte Sitzung gestartet.",
- "wrong_password": "Passwort flasch.",
+ "wrong_password": "Passwort falsch.",
"protecting-finished-successfully": "Geschützt erfolgreich beendet.",
"unprotecting-finished-successfully": "Ungeschützt erfolgreich beendet.",
"protecting-in-progress": "Schützen läuft: {{count}}",
@@ -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.",
@@ -1658,7 +1654,7 @@
"add-term-to-dictionary": "Begriff \"{{term}}\" zum Wörterbuch hinzufügen",
"cut": "Ausschneiden",
"copy": "Kopieren",
- "copy-link": "Link opieren",
+ "copy-link": "Link kopieren",
"paste": "Einfügen",
"paste-as-plain-text": "Als unformatierten Text einfügen",
"search_online": "Suche nach \"{{term}}\" mit {{searchEngine}} starten"
diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json
index cff40010f..2150a8599 100644
--- a/apps/client/src/translations/en/translation.json
+++ b/apps/client/src/translations/en/translation.json
@@ -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",
@@ -104,7 +107,8 @@
"export_status": "Export status",
"export_in_progress": "Export in progress: {{progressCount}}",
"export_finished_successfully": "Export finished successfully.",
- "format_pdf": "PDF - for printing or sharing purposes."
+ "format_pdf": "PDF - for printing or sharing purposes.",
+ "share-format": "HTML for web publishing - uses the same theme that is used shared notes, but can be published as a static website."
},
"help": {
"title": "Cheatsheet",
@@ -260,7 +264,6 @@
"delete_all_revisions": "Delete all revisions of this note",
"delete_all_button": "Delete all revisions",
"help_title": "Help on Note Revisions",
- "revision_last_edited": "This revision was last edited on {{date}}",
"confirm_delete_all": "Do you want to delete all revisions of this note?",
"no_revisions": "No revisions for this note yet...",
"restore_button": "Restore",
@@ -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.",
@@ -2038,6 +2037,9 @@
"start-presentation": "Start presentation",
"slide-overview": "Toggle an overview of the slides"
},
+ "calendar_view": {
+ "delete_note": "Delete note..."
+ },
"command_palette": {
"tree-action-name": "Tree: {{name}}",
"export_note_title": "Export Note",
diff --git a/apps/client/src/translations/es/translation.json b/apps/client/src/translations/es/translation.json
index ce70f474b..5a38d62ee 100644
--- a/apps/client/src/translations/es/translation.json
+++ b/apps/client/src/translations/es/translation.json
@@ -104,7 +104,8 @@
"export_status": "Estado de exportación",
"export_in_progress": "Exportación en curso: {{progressCount}}",
"export_finished_successfully": "La exportación finalizó exitosamente.",
- "format_pdf": "PDF - para propósitos de impresión o compartición."
+ "format_pdf": "PDF - para propósitos de impresión o compartición.",
+ "share-format": "HTML para publicación web: utiliza el mismo tema que se utiliza en las notas compartidas, pero se puede publicar como un sitio web estático."
},
"help": {
"noteNavigation": "Navegación de notas",
@@ -184,7 +185,8 @@
},
"import-status": "Estado de importación",
"in-progress": "Importación en progreso: {{progress}}",
- "successful": "Importación finalizada exitosamente."
+ "successful": "Importación finalizada exitosamente.",
+ "importZipRecommendation": "Al importar un archivo ZIP, la jerarquía de notas reflejará la estructura de subdirectorios dentro del archivo comprimido."
},
"include_note": {
"dialog_title": "Incluir nota",
@@ -259,7 +261,6 @@
"delete_all_revisions": "Eliminar todas las revisiones de esta nota",
"delete_all_button": "Eliminar todas las revisiones",
"help_title": "Ayuda sobre revisiones de notas",
- "revision_last_edited": "Esta revisión se editó por última vez en {{date}}",
"confirm_delete_all": "¿Quiere eliminar todas las revisiones de esta nota?",
"no_revisions": "Aún no hay revisiones para esta nota...",
"restore_button": "Restaurar",
@@ -1445,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.",
@@ -1715,7 +1712,9 @@
"window-on-top": "Mantener esta ventana en la parte superior"
},
"note_detail": {
- "could_not_find_typewidget": "No se pudo encontrar typeWidget para el tipo '{{type}}'"
+ "could_not_find_typewidget": "No se pudo encontrar typeWidget para el tipo '{{type}}'",
+ "printing": "Impresión en curso...",
+ "printing_pdf": "Exportando a PDF en curso.."
},
"note_title": {
"placeholder": "escriba el título de la nota aquí..."
diff --git a/apps/client/src/translations/fr/translation.json b/apps/client/src/translations/fr/translation.json
index 2c12c9bff..46d6575d6 100644
--- a/apps/client/src/translations/fr/translation.json
+++ b/apps/client/src/translations/fr/translation.json
@@ -260,7 +260,6 @@
"delete_all_revisions": "Supprimer toutes les versions de cette note",
"delete_all_button": "Supprimer toutes les versions",
"help_title": "Aide sur les versions de notes",
- "revision_last_edited": "Cette version a été modifiée pour la dernière fois le {{date}}",
"confirm_delete_all": "Voulez-vous supprimer toutes les versions de cette note ?",
"no_revisions": "Aucune version pour cette note pour l'instant...",
"confirm_restore": "Voulez-vous restaurer cette version ? Le titre et le contenu actuels de la note seront écrasés par cette version.",
@@ -1289,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.",
@@ -1307,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",
diff --git a/apps/client/src/translations/hi/translation.json b/apps/client/src/translations/hi/translation.json
new file mode 100644
index 000000000..2d1c3b9f1
--- /dev/null
+++ b/apps/client/src/translations/hi/translation.json
@@ -0,0 +1,5 @@
+{
+ "about": {
+ "title": "ट्रिलियम नोट्स के बारें में"
+ }
+}
diff --git a/apps/client/src/translations/it/translation.json b/apps/client/src/translations/it/translation.json
index a49a9a5dc..52c029ffb 100644
--- a/apps/client/src/translations/it/translation.json
+++ b/apps/client/src/translations/it/translation.json
@@ -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.",
@@ -867,7 +864,6 @@
"delete_all_revisions": "Elimina tutte le revisioni di questa nota",
"delete_all_button": "Elimina tutte le revisioni",
"help_title": "Aiuto sulle revisioni delle note",
- "revision_last_edited": "Questa revisione è stata modificata l'ultima volta il {{date}}",
"confirm_delete_all": "Vuoi eliminare tutte le revisioni di questa nota?",
"no_revisions": "Ancora nessuna revisione per questa nota...",
"restore_button": "Ripristina",
diff --git a/apps/client/src/translations/ja/translation.json b/apps/client/src/translations/ja/translation.json
index 1f81a4a4f..6a9b4f75e 100644
--- a/apps/client/src/translations/ja/translation.json
+++ b/apps/client/src/translations/ja/translation.json
@@ -254,7 +254,8 @@
"export_status": "エクスポート状況",
"export_in_progress": "エクスポート処理中: {{progressCount}}",
"export_finished_successfully": "エクスポートが正常に完了しました。",
- "format_pdf": "PDF - 印刷または共有目的に。"
+ "format_pdf": "PDF - 印刷または共有目的に。",
+ "share-format": "Web 公開用の HTML - 共有ノートで使用されるのと同じテーマを使用しますが、静的 Web サイトとして公開できます。"
},
"help": {
"title": "チートシート",
@@ -610,7 +611,6 @@
"delete_all_revisions": "このノートの変更履歴をすべて削除",
"delete_all_button": "変更履歴をすべて削除",
"help_title": "変更履歴のヘルプ",
- "revision_last_edited": "この変更は{{date}}に行われました",
"confirm_delete_all": "このノートのすべての変更履歴を削除しますか?",
"no_revisions": "このノートに変更履歴はまだありません...",
"restore_button": "復元",
@@ -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": "トークンはまだありません。上のボタンをクリックして作成してください。",
diff --git a/apps/website/public/translations/ca/translation.json b/apps/client/src/translations/mr/translation.json
similarity index 100%
rename from apps/website/public/translations/ca/translation.json
rename to apps/client/src/translations/mr/translation.json
diff --git a/apps/client/src/translations/nl/translation.json b/apps/client/src/translations/nl/translation.json
index d07df69e5..de5cfb6c7 100644
--- a/apps/client/src/translations/nl/translation.json
+++ b/apps/client/src/translations/nl/translation.json
@@ -13,6 +13,13 @@
"critical-error": {
"title": "Kritische Error",
"message": "Een kritieke fout heeft plaatsgevonden waardoor de cliënt zich aanmeldt vanaf het begin:\n\n84X\n\nDit is waarschijnlijk veroorzaakt door een script dat op een onverwachte manier faalt. Probeer de sollicitatie in veilige modus te starten en de kwestie aan te spreken."
+ },
+ "widget-error": {
+ "title": "Starten widget mislukt",
+ "message-unknown": "Onbekende widget kan niet gestart worden omdat:\n\n{{message}}"
+ },
+ "bundle-error": {
+ "title": "Custom script laden mislukt"
}
},
"add_link": {
diff --git a/apps/client/src/translations/pl/translation.json b/apps/client/src/translations/pl/translation.json
index 1d90a30e8..6ff4b26f4 100644
--- a/apps/client/src/translations/pl/translation.json
+++ b/apps/client/src/translations/pl/translation.json
@@ -912,7 +912,6 @@
"delete_all_revisions": "Usuń wszystkie wersje tej notatki",
"delete_all_button": "Usuń wszystkie wersje",
"help_title": "Pomoc dotycząca wersji notatki",
- "revision_last_edited": "Ta wersja była ostatnio edytowana {{date}}",
"confirm_delete_all": "Czy chcesz usunąć wszystkie wersje tej notatki?",
"no_revisions": "Brak wersji dla tej notatki...",
"restore_button": "Przywróć",
@@ -1664,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.",
diff --git a/apps/client/src/translations/pt/translation.json b/apps/client/src/translations/pt/translation.json
index ce5e69f14..9b07163be 100644
--- a/apps/client/src/translations/pt/translation.json
+++ b/apps/client/src/translations/pt/translation.json
@@ -259,7 +259,6 @@
"delete_all_revisions": "Apagar todas as versões desta nota",
"delete_all_button": "Apagar todas as versões",
"help_title": "Ajuda sobre as versões da nota",
- "revision_last_edited": "Esta versão foi editada pela última vez em {{date}}",
"confirm_delete_all": "Quer apagar todas as versões desta nota?",
"no_revisions": "Ainda não há versões para esta nota...",
"restore_button": "Recuperar",
@@ -1423,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.",
diff --git a/apps/client/src/translations/pt_br/translation.json b/apps/client/src/translations/pt_br/translation.json
index 5bdd1ae54..ae4503508 100644
--- a/apps/client/src/translations/pt_br/translation.json
+++ b/apps/client/src/translations/pt_br/translation.json
@@ -415,7 +415,6 @@
"delete_all_revisions": "Excluir todas as versões desta nota",
"delete_all_button": "Excluir todas as versões",
"help_title": "Ajuda sobre as versões da nota",
- "revision_last_edited": "Esta versão foi editada pela última vez em {{date}}",
"confirm_delete_all": "Você quer excluir todas as versões desta nota?",
"no_revisions": "Ainda não há versões para esta nota...",
"restore_button": "Recuperar",
@@ -1933,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.",
diff --git a/apps/client/src/translations/ro/translation.json b/apps/client/src/translations/ro/translation.json
index 14350bc83..dbb9e0955 100644
--- a/apps/client/src/translations/ro/translation.json
+++ b/apps/client/src/translations/ro/translation.json
@@ -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:",
@@ -1090,7 +1086,6 @@
"preview_not_available": "Nu este disponibilă o previzualizare pentru acest tip de notiță.",
"restore_button": "Restaurează",
"revision_deleted": "Revizia notiței a fost ștearsă.",
- "revision_last_edited": "Revizia a fost ultima oară modificată pe {{date}}",
"revision_restored": "Revizia notiței a fost restaurată.",
"revisions_deleted": "Notița reviziei a fost ștearsă.",
"maximum_revisions": "Numărul maxim de revizii pentru notița curentă: {{number}}.",
diff --git a/apps/client/src/translations/ru/translation.json b/apps/client/src/translations/ru/translation.json
index 07c323a89..463fc8961 100644
--- a/apps/client/src/translations/ru/translation.json
+++ b/apps/client/src/translations/ru/translation.json
@@ -366,7 +366,6 @@
"delete_all_button": "Удалить все версии",
"help_title": "Помощь по версиям заметок",
"confirm_delete_all": "Вы хотите удалить все версии этой заметки?",
- "revision_last_edited": "Эта версия последний раз редактировалась {{date}}",
"confirm_restore": "Хотите восстановить эту версию? Текущее название и содержание заметки будут перезаписаны этой версией.",
"confirm_delete": "Вы хотите удалить эту версию?",
"revisions_deleted": "Версии заметки были удалены.",
@@ -1441,7 +1440,6 @@
},
"etapi": {
"title": "ETAPI",
- "wiki": "вики",
"created": "Создано",
"actions": "Действия",
"existing_tokens": "Существующие токены",
@@ -1449,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": "Переименовать этот токен",
diff --git a/apps/client/src/translations/sr/translation.json b/apps/client/src/translations/sr/translation.json
index dd0ff6ff9..df88fdcda 100644
--- a/apps/client/src/translations/sr/translation.json
+++ b/apps/client/src/translations/sr/translation.json
@@ -256,7 +256,6 @@
"delete_all_revisions": "Obriši sve revizije ove beleške",
"delete_all_button": "Obriši sve revizije",
"help_title": "Pomoć za Revizije beleški",
- "revision_last_edited": "Ova revizija je poslednji put izmenjena {{date}}",
"confirm_delete_all": "Da li želite da obrišete sve revizije ove beleške?",
"no_revisions": "Još uvek nema revizija za ovu belešku...",
"restore_button": "Vrati",
diff --git a/apps/client/src/translations/tw/translation.json b/apps/client/src/translations/tw/translation.json
index 12a3bd68d..3c3a45825 100644
--- a/apps/client/src/translations/tw/translation.json
+++ b/apps/client/src/translations/tw/translation.json
@@ -104,7 +104,8 @@
"export_in_progress": "正在匯出:{{progressCount}}",
"export_finished_successfully": "成功匯出。",
"format_html": "HTML - 推薦,因為它保留了所有格式",
- "format_pdf": "PDF - 用於列印或與他人分享。"
+ "format_pdf": "PDF - 用於列印或與他人分享。",
+ "share-format": "HTML 網頁發佈——使用與共享筆記相同的佈景主題,但可發佈為靜態網站。"
},
"help": {
"noteNavigation": "筆記導航",
@@ -260,7 +261,6 @@
"delete_all_revisions": "刪除此筆記的所有歷史版本",
"delete_all_button": "刪除所有歷史版本",
"help_title": "關於筆記歷史版本的說明",
- "revision_last_edited": "此歷史版本上次於 {{date}} 編輯",
"confirm_delete_all": "您是否要刪除此筆記的所有歷史版本?",
"no_revisions": "此筆記暫無歷史版本…",
"confirm_restore": "您是否要還原此歷史版本?這將使用此歷史版本覆寫筆記的目前標題和內容。",
@@ -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": "選項狀態",
diff --git a/apps/client/src/translations/uk/translation.json b/apps/client/src/translations/uk/translation.json
index 9166599e1..9e99e6d09 100644
--- a/apps/client/src/translations/uk/translation.json
+++ b/apps/client/src/translations/uk/translation.json
@@ -309,7 +309,6 @@
"delete_all_revisions": "Видалити всі версії цієї нотатки",
"delete_all_button": "Видалити всі версії",
"help_title": "Довідка щодо Версій нотаток",
- "revision_last_edited": "Цю версію востаннє редагували {{date}}",
"confirm_delete_all": "Ви хочете видалити всі версії цієї нотатки?",
"no_revisions": "Поки що немає версій цієї нотатки...",
"restore_button": "Відновити",
@@ -1403,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": "Токенів поки що немає. Натисніть кнопку вище, щоб створити його.",
diff --git a/apps/client/src/types.d.ts b/apps/client/src/types.d.ts
index f549680a7..c386a67f0 100644
--- a/apps/client/src/types.d.ts
+++ b/apps/client/src/types.d.ts
@@ -26,7 +26,6 @@ interface CustomGlobals {
appContext: AppContext;
froca: Froca;
treeCache: Froca;
- importMarkdownInline: () => Promise;
SEARCH_HELP_TEXT: string;
activeDialog: JQuery | null;
componentId: string;
diff --git a/apps/client/src/widgets/collections/calendar/context_menu.ts b/apps/client/src/widgets/collections/calendar/context_menu.ts
new file mode 100644
index 000000000..7eddbed3c
--- /dev/null
+++ b/apps/client/src/widgets/collections/calendar/context_menu.ts
@@ -0,0 +1,28 @@
+import FNote from "../../../entities/fnote";
+import contextMenu, { ContextMenuEvent } from "../../../menus/context_menu";
+import link_context_menu from "../../../menus/link_context_menu";
+import branches from "../../../services/branches";
+import { t } from "../../../services/i18n";
+
+export function openCalendarContextMenu(e: ContextMenuEvent, noteId: string, parentNote: FNote) {
+ e.preventDefault();
+ e.stopPropagation();
+
+ contextMenu.show({
+ x: e.pageX,
+ y: e.pageY,
+ items: [
+ ...link_context_menu.getItems(),
+ { kind: "separator" },
+ {
+ title: t("calendar_view.delete_note"),
+ uiIcon: "bx bx-trash",
+ handler: async () => {
+ const branchId = parentNote.childToBranch[noteId];
+ await branches.deleteNotes([ branchId ], false, false);
+ }
+ }
+ ],
+ selectMenuItemHandler: ({ command }) => link_context_menu.handleLinkContextMenuItem(command, noteId),
+ })
+}
diff --git a/apps/client/src/widgets/collections/calendar/index.tsx b/apps/client/src/widgets/collections/calendar/index.tsx
index 518af914c..692b074a6 100644
--- a/apps/client/src/widgets/collections/calendar/index.tsx
+++ b/apps/client/src/widgets/collections/calendar/index.tsx
@@ -20,6 +20,7 @@ import Button, { ButtonGroup } from "../../react/Button";
import ActionButton from "../../react/ActionButton";
import { RefObject } from "preact";
import TouchBar, { TouchBarButton, TouchBarLabel, TouchBarSegmentedControl, TouchBarSpacer } from "../../react/TouchBar";
+import { openCalendarContextMenu } from "./context_menu";
interface CalendarViewData {
@@ -106,7 +107,7 @@ export default function CalendarView({ note, noteIds }: ViewModeProps {
const { iconClass, promotedAttributes } = e.event.extendedProps;
@@ -302,6 +303,11 @@ function useEventDisplayCustomization() {
}
$(mainContainer ?? e.el).append($(promotedAttributesHtml));
}
+
+ e.el.addEventListener("contextmenu", (contextMenuEvent) => {
+ const noteId = e.event.extendedProps.noteId;
+ openCalendarContextMenu(contextMenuEvent, noteId, parentNote);
+ });
}, []);
return { eventDidMount };
}
diff --git a/apps/client/src/widgets/dialogs/branch_prefix.css b/apps/client/src/widgets/dialogs/branch_prefix.css
new file mode 100644
index 000000000..3470f1018
--- /dev/null
+++ b/apps/client/src/widgets/dialogs/branch_prefix.css
@@ -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;
+}
diff --git a/apps/client/src/widgets/dialogs/branch_prefix.tsx b/apps/client/src/widgets/dialogs/branch_prefix.tsx
index 46888f0ab..e715c894f 100644
--- a/apps/client/src/widgets/dialogs/branch_prefix.tsx
+++ b/apps/client/src/widgets/dialogs/branch_prefix.tsx
@@ -10,53 +10,86 @@ import Button from "../react/Button.jsx";
import FormGroup from "../react/FormGroup.js";
import { useTriliumEvent } from "../react/hooks.jsx";
import FBranch from "../../entities/fbranch.js";
+import type { ContextMenuCommandData } from "../../components/app_context.js";
+import "./branch_prefix.css";
+
+// Virtual branches (e.g., from search results) start with this prefix
+const VIRTUAL_BRANCH_PREFIX = "virt-";
export default function BranchPrefixDialog() {
const [ shown, setShown ] = useState(false);
- const [ branch, setBranch ] = useState();
+ const [ branches, setBranches ] = useState([]);
const [ prefix, setPrefix ] = useState("");
const branchInput = useRef(null);
- useTriliumEvent("editBranchPrefix", async () => {
- const notePath = appContext.tabManager.getActiveContextNotePath();
- if (!notePath) {
+ useTriliumEvent("editBranchPrefix", async (data?: ContextMenuCommandData) => {
+ let branchIds: string[] = [];
+
+ if (data?.selectedOrActiveBranchIds && data.selectedOrActiveBranchIds.length > 0) {
+ // Multi-select mode from tree context menu
+ branchIds = data.selectedOrActiveBranchIds.filter((branchId) => !branchId.startsWith(VIRTUAL_BRANCH_PREFIX));
+ } else {
+ // Single branch mode from keyboard shortcut or when no selection
+ const notePath = appContext.tabManager.getActiveContextNotePath();
+ if (!notePath) {
+ return;
+ }
+
+ const { noteId, parentNoteId } = tree.getNoteIdAndParentIdFromUrl(notePath);
+
+ if (!noteId || !parentNoteId) {
+ return;
+ }
+
+ const branchId = await froca.getBranchId(parentNoteId, noteId);
+ if (!branchId) {
+ return;
+ }
+ const parentNote = await froca.getNote(parentNoteId);
+ if (!parentNote || parentNote.type === "search") {
+ return;
+ }
+
+ branchIds = [branchId];
+ }
+
+ if (branchIds.length === 0) {
return;
}
- const { noteId, parentNoteId } = tree.getNoteIdAndParentIdFromUrl(notePath);
+ const newBranches = branchIds
+ .map(id => froca.getBranch(id))
+ .filter((branch): branch is FBranch => branch !== null);
- if (!noteId || !parentNoteId) {
+ if (newBranches.length === 0) {
return;
}
- const newBranchId = await froca.getBranchId(parentNoteId, noteId);
- if (!newBranchId) {
- return;
- }
- const parentNote = await froca.getNote(parentNoteId);
- if (!parentNote || parentNote.type === "search") {
- return;
- }
-
- const newBranch = froca.getBranch(newBranchId);
- setBranch(newBranch);
- setPrefix(newBranch?.prefix ?? "");
+ setBranches(newBranches);
+ // Use the prefix of the first branch as the initial value
+ setPrefix(newBranches[0]?.prefix ?? "");
setShown(true);
});
async function onSubmit() {
- if (!branch) {
+ if (branches.length === 0) {
return;
}
- savePrefix(branch.branchId, prefix);
+ if (branches.length === 1) {
+ await savePrefix(branches[0].branchId, prefix);
+ } else {
+ await savePrefixBatch(branches.map(b => b.branchId), prefix);
+ }
setShown(false);
}
+ const isSingleBranch = branches.length === 1;
+
return (
branchInput.current?.focus()}
onHidden={() => setShown(false)}
@@ -69,9 +102,27 @@ export default function BranchPrefixDialog() {
+ {!isSingleBranch && (
+
+
{t("branch_prefix.affected_branches", { count: branches.length })}
+
+ {branches.map((branch) => {
+ const note = branch.getNoteFromCache();
+ return (
+
+ {branch.prefix && {branch.prefix} - }
+ {note.title}
+
+ );
+ })}
+
+
+ )}
);
}
@@ -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 }));
+}
diff --git a/apps/client/src/widgets/dialogs/export.tsx b/apps/client/src/widgets/dialogs/export.tsx
index dded32624..b694d9abe 100644
--- a/apps/client/src/widgets/dialogs/export.tsx
+++ b/apps/client/src/widgets/dialogs/export.tsx
@@ -79,6 +79,7 @@ export default function ExportDialog() {
values={[
{ value: "html", label: t("export.format_html_zip") },
{ value: "markdown", label: t("export.format_markdown") },
+ { value: "share", label: t("export.share-format") },
{ value: "opml", label: t("export.format_opml") }
]}
/>
diff --git a/apps/client/src/widgets/dialogs/markdown_import.tsx b/apps/client/src/widgets/dialogs/markdown_import.tsx
index d14d6fb11..8de49e330 100644
--- a/apps/client/src/widgets/dialogs/markdown_import.tsx
+++ b/apps/client/src/widgets/dialogs/markdown_import.tsx
@@ -1,5 +1,4 @@
-import { useCallback, useRef, useState } from "preact/hooks";
-import appContext from "../../components/app_context";
+import { useRef, useState } from "preact/hooks";
import { t } from "../../services/i18n";
import server from "../../services/server";
import toast from "../../services/toast";
@@ -7,6 +6,12 @@ import utils from "../../services/utils";
import Modal from "../react/Modal";
import Button from "../react/Button";
import { useTriliumEvent } from "../react/hooks";
+import EditableTextTypeWidget from "../type_widgets/editable_text";
+import { CKEditorApi } from "../type_widgets/text/CKEditorWithWatchdog";
+
+export interface MarkdownImportOpts {
+ editorApi: CKEditorApi;
+}
interface RenderMarkdownResponse {
htmlContent: string;
@@ -14,39 +19,36 @@ interface RenderMarkdownResponse {
export default function MarkdownImportDialog() {
const markdownImportTextArea = useRef(null);
+ const editorApiRef = useRef(null);
+ const [ textTypeWidget, setTextTypeWidget ] = useState();
const [ text, setText ] = useState("");
const [ shown, setShown ] = useState(false);
- const triggerImport = useCallback(() => {
- if (appContext.tabManager.getActiveContextNoteType() !== "text") {
- return;
- }
-
+ useTriliumEvent("showPasteMarkdownDialog", ({ editorApi }) => {
+ setTextTypeWidget(textTypeWidget);
if (utils.isElectron()) {
const { clipboard } = utils.dynamicRequire("electron");
const text = clipboard.readText();
-
- convertMarkdownToHtml(text);
+
+ convertMarkdownToHtml(text, editorApi);
} else {
+ editorApiRef.current = editorApi;
setShown(true);
}
- }, []);
-
- useTriliumEvent("importMarkdownInline", triggerImport);
- useTriliumEvent("pasteMarkdownIntoText", triggerImport);
-
- async function sendForm() {
- await convertMarkdownToHtml(text);
- setText("");
- setShown(false);
- }
+ });
return (
}
+ footer={ setShown(false)} keyboardShortcut="Ctrl+Enter" />}
onShown={() => markdownImportTextArea.current?.focus()}
- onHidden={() => setShown(false) }
+ onHidden={async () => {
+ if (editorApiRef.current) {
+ await convertMarkdownToHtml(text, editorApiRef.current);
+ }
+ setShown(false);
+ setText("");
+ }}
show={shown}
>
{t("markdown_import.modal_body_text")}
@@ -56,26 +58,15 @@ export default function MarkdownImportDialog() {
onKeyDown={(e) => {
if (e.key === "Enter" && e.ctrlKey) {
e.preventDefault();
- sendForm();
+ setShown(false);
}
}}>
)
}
-async function convertMarkdownToHtml(markdownContent: string) {
+async function convertMarkdownToHtml(markdownContent: string, textTypeWidget: CKEditorApi) {
const { htmlContent } = await server.post("other/render-markdown", { markdownContent });
-
- const textEditor = await appContext.tabManager.getActiveContext()?.getTextEditor();
- if (!textEditor) {
- return;
- }
-
- const viewFragment = textEditor.data.processor.toView(htmlContent);
- const modelFragment = textEditor.data.toModel(viewFragment);
-
- textEditor.model.insertContent(modelFragment, textEditor.model.document.selection);
- textEditor.editing.view.focus();
-
+ textTypeWidget.addHtmlToEditor(htmlContent);
toast.showMessage(t("markdown_import.import_success"));
-}
\ No newline at end of file
+}
diff --git a/apps/client/src/widgets/dialogs/popup_editor.ts b/apps/client/src/widgets/dialogs/popup_editor.ts
index 128aadc6e..26ea0e2ab 100644
--- a/apps/client/src/widgets/dialogs/popup_editor.ts
+++ b/apps/client/src/widgets/dialogs/popup_editor.ts
@@ -154,6 +154,11 @@ export default class PopupEditorDialog extends Container {
return Promise.resolve();
}
+ // Avoid not showing recent notes when creating a new empty tab.
+ if ("noteContext" in data && data.noteContext.ntxId !== "_popup-editor") {
+ return Promise.resolve();
+ }
+
return super.handleEventInChildren(name, data);
}
diff --git a/apps/client/src/widgets/dialogs/revisions.tsx b/apps/client/src/widgets/dialogs/revisions.tsx
index b21a36faf..0effd8b08 100644
--- a/apps/client/src/widgets/dialogs/revisions.tsx
+++ b/apps/client/src/widgets/dialogs/revisions.tsx
@@ -140,11 +140,10 @@ function RevisionsList({ revisions, onSelect, currentRevision }: { revisions: Re
{revisions.map((item) =>
- {item.dateLastEdited && item.dateLastEdited.substr(0, 16)} ({item.contentLength && utils.formatSize(item.contentLength)})
+ {item.dateCreated && item.dateCreated.substr(0, 16)} ({item.contentLength && utils.formatSize(item.contentLength)})
)}
);
diff --git a/apps/client/src/widgets/note_tree.ts b/apps/client/src/widgets/note_tree.ts
index f1c2ca736..cb2120687 100644
--- a/apps/client/src/widgets/note_tree.ts
+++ b/apps/client/src/widgets/note_tree.ts
@@ -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;
diff --git a/apps/client/src/widgets/ribbon/EditedNotesTab.tsx b/apps/client/src/widgets/ribbon/EditedNotesTab.tsx
index 5bab1c816..4bdae4126 100644
--- a/apps/client/src/widgets/ribbon/EditedNotesTab.tsx
+++ b/apps/client/src/widgets/ribbon/EditedNotesTab.tsx
@@ -13,8 +13,8 @@ export default function EditedNotesTab({ note }: TabContext) {
useEffect(() => {
if (!note) return;
server.get(`edited-notes/${note.getLabelValue("dateNote")}`).then(async editedNotes => {
- editedNotes = editedNotes.filter((n) => n.noteId !== note.noteId);
- const noteIds = editedNotes.flatMap((n) => n.noteId);
+ editedNotes = editedNotes.filter((n) => n.noteId !== note.noteId);
+ const noteIds = editedNotes.flatMap((n) => n.noteId);
await froca.getNotes(noteIds, true); // preload all at once
setEditedNotes(editedNotes);
});
@@ -41,11 +41,11 @@ export default function EditedNotesTab({ note }: TabContext) {
)}
)
- }))}
+ }), " ")}
) : (
{t("edited_notes.no_edited_notes_found")}
)}
- )
+ )
}
diff --git a/apps/client/src/widgets/ribbon/style.css b/apps/client/src/widgets/ribbon/style.css
index 26e25154e..16437d618 100644
--- a/apps/client/src/widgets/ribbon/style.css
+++ b/apps/client/src/widgets/ribbon/style.css
@@ -264,7 +264,6 @@
position: absolute;
inset-inline-end: 5px;
bottom: 5px;
- z-index: 1000;
}
.style-resolver {
diff --git a/apps/client/src/widgets/type_widgets/options/components/OptionsSection.tsx b/apps/client/src/widgets/type_widgets/options/components/OptionsSection.tsx
index ff78d8a33..ce42b66e3 100644
--- a/apps/client/src/widgets/type_widgets/options/components/OptionsSection.tsx
+++ b/apps/client/src/widgets/type_widgets/options/components/OptionsSection.tsx
@@ -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;
diff --git a/apps/client/src/widgets/type_widgets/options/etapi.tsx b/apps/client/src/widgets/type_widgets/options/etapi.tsx
index 3ee7c7e19..f49dc85f7 100644
--- a/apps/client/src/widgets/type_widgets/options/etapi.tsx
+++ b/apps/client/src/widgets/type_widgets/options/etapi.tsx
@@ -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;
type DeleteTokenCallback = (tokenId: string, name: string ) => Promise;
@@ -48,19 +49,13 @@ export default function EtapiSettings() {
message: t("etapi.token_created_message"),
defaultValue: authToken
});
- }, []);
+ }, []);
return (
- {t("etapi.description")}
- ${t("etapi.wiki")}`,
- // 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: `${t("etapi.openapi_spec")} `,
- link_to_swagger_ui: `${t("etapi.swagger_ui")} `
- })} />
+ {t("etapi.description")}
+
+
{t("etapi.existing_tokens")}
@@ -123,7 +119,7 @@ function TokenList({ tokens }: { tokens: EtapiToken[] }) {
text={t("etapi.rename_token")}
onClick={() => renameCallback(etapiTokenId, name)}
/>
-
+
-
-
+
+
);
}
diff --git a/apps/client/src/widgets/type_widgets/text/CKEditorWithWatchdog.tsx b/apps/client/src/widgets/type_widgets/text/CKEditorWithWatchdog.tsx
index ec3716831..a90620711 100644
--- a/apps/client/src/widgets/type_widgets/text/CKEditorWithWatchdog.tsx
+++ b/apps/client/src/widgets/type_widgets/text/CKEditorWithWatchdog.tsx
@@ -13,6 +13,7 @@ export interface CKEditorApi {
getSelectedText(): string;
addLink(notePath: string, linkTitle: string | null, externalLink?: boolean): void;
addLinkToEditor(linkHref: string, linkTitle: string): void;
+ addHtmlToEditor(html: string): void;
addIncludeNote(noteId: string, boxSize?: BoxSize): void;
addImage(noteId: string): Promise;
}
@@ -100,6 +101,26 @@ export default function CKEditorWithWatchdog({ containerRef: externalContainerRe
);
});
},
+ addHtmlToEditor(html: string) {
+ const editor = watchdogRef.current?.editor;
+ if (!editor) return;
+
+ editor.model.change((writer) => {
+ const viewFragment = editor.data.processor.toView(html);
+ const modelFragment = editor.data.toModel(viewFragment);
+ const insertPosition = editor.model.document.selection.getLastPosition();
+
+ if (insertPosition) {
+ const range = editor.model.insertContent(modelFragment, insertPosition);
+
+ if (range) {
+ writer.setSelection(range.end);
+ }
+ }
+ });
+
+ editor.editing.view.focus();
+ },
async addImage(noteId) {
const editor = watchdogRef.current?.editor;
if (!editor) return;
diff --git a/apps/client/src/widgets/type_widgets/text/EditableText.tsx b/apps/client/src/widgets/type_widgets/text/EditableText.tsx
index 388da1691..3550546c7 100644
--- a/apps/client/src/widgets/type_widgets/text/EditableText.tsx
+++ b/apps/client/src/widgets/type_widgets/text/EditableText.tsx
@@ -90,6 +90,12 @@ export default function EditableText({ note, parentComponent, ntxId, noteContext
}
});
},
+ pasteMarkdownIntoTextCommand() {
+ if (!editorApiRef.current) return;
+ parentComponent?.triggerCommand("showPasteMarkdownDialog", {
+ editorApi: editorApiRef.current,
+ });
+ },
// Include note functionality note
addIncludeNoteToTextCommand() {
if (!editorApiRef.current) return;
diff --git a/apps/client/vite.config.mts b/apps/client/vite.config.mts
index 5a3053915..8d3b0c583 100644
--- a/apps/client/vite.config.mts
+++ b/apps/client/vite.config.mts
@@ -74,7 +74,6 @@ export default defineConfig(() => ({
mobile: join(__dirname, "src", "mobile.ts"),
login: join(__dirname, "src", "login.ts"),
setup: join(__dirname, "src", "setup.ts"),
- share: join(__dirname, "src", "share.ts"),
set_password: join(__dirname, "src", "set_password.ts"),
runtime: join(__dirname, "src", "runtime.ts"),
print: join(__dirname, "src", "print.tsx")
@@ -84,7 +83,8 @@ export default defineConfig(() => ({
chunkFileNames: "src/[name].js",
assetFileNames: "src/[name].[ext]",
manualChunks: {
- "ckeditor5": [ "@triliumnext/ckeditor5" ]
+ "ckeditor5": [ "@triliumnext/ckeditor5" ],
+ "boxicons": [ "../../node_modules/boxicons/css/boxicons.min.css" ]
},
},
onwarn(warning, rollupWarn) {
diff --git a/apps/desktop/electron-forge/trilium-no-cert-check.bat b/apps/desktop/electron-forge/trilium-no-cert-check.bat
index cfc345c42..d3405b497 100644
--- a/apps/desktop/electron-forge/trilium-no-cert-check.bat
+++ b/apps/desktop/electron-forge/trilium-no-cert-check.bat
@@ -6,7 +6,7 @@ WHERE powershell.exe > NUL 2>&1
IF %ERRORLEVEL% NEQ 0 GOTO BATCH ELSE GOTO POWERSHELL
:POWERSHELL
-powershell -ExecutionPolicy Bypass -NonInteractive -NoLogo "Set-Item -Path Env:NODE_TLS_REJECT_UNAUTHORIZED -Value 0; ./trilium.exe"
+powershell -ExecutionPolicy Bypass -NonInteractive -NoLogo -Command "Set-Item -Path Env:NODE_TLS_REJECT_UNAUTHORIZED -Value 0; ./trilium.exe"
GOTO END
:BATCH
diff --git a/apps/desktop/electron-forge/trilium-portable.bat b/apps/desktop/electron-forge/trilium-portable.bat
index b6eeb853f..5b71c9cf2 100644
--- a/apps/desktop/electron-forge/trilium-portable.bat
+++ b/apps/desktop/electron-forge/trilium-portable.bat
@@ -6,7 +6,7 @@ WHERE powershell.exe > NUL 2>&1
IF %ERRORLEVEL% NEQ 0 GOTO BATCH ELSE GOTO POWERSHELL
:POWERSHELL
-powershell -ExecutionPolicy Bypass -NonInteractive -NoLogo "Set-Item -Path Env:TRILIUM_DATA_DIR -Value './trilium-data'; ./trilium.exe"
+powershell -ExecutionPolicy Bypass -NonInteractive -NoLogo -Command "Set-Item -Path Env:TRILIUM_DATA_DIR -Value './trilium-data'; ./trilium.exe"
GOTO END
:BATCH
diff --git a/apps/desktop/electron-forge/trilium-safe-mode.bat b/apps/desktop/electron-forge/trilium-safe-mode.bat
index 7e0fafa99..e112896f7 100644
--- a/apps/desktop/electron-forge/trilium-safe-mode.bat
+++ b/apps/desktop/electron-forge/trilium-safe-mode.bat
@@ -6,7 +6,7 @@ WHERE powershell.exe > NUL 2>&1
IF %ERRORLEVEL% NEQ 0 GOTO BATCH ELSE GOTO POWERSHELL
:POWERSHELL
-powershell -ExecutionPolicy Bypass -NonInteractive -NoLogo "Set-Item -Path Env:TRILIUM_SAFE_MODE -Value 1; ./trilium.exe --disable-gpu"
+powershell -ExecutionPolicy Bypass -NonInteractive -NoLogo -Command "Set-Item -Path Env:TRILIUM_SAFE_MODE -Value 1; ./trilium.exe --disable-gpu"
GOTO END
:BATCH
diff --git a/apps/desktop/package.json b/apps/desktop/package.json
index a59f17e12..5cf38b041 100644
--- a/apps/desktop/package.json
+++ b/apps/desktop/package.json
@@ -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",
diff --git a/apps/desktop/scripts/build.ts b/apps/desktop/scripts/build.ts
index 945a4cfeb..0b76d8b25 100644
--- a/apps/desktop/scripts/build.ts
+++ b/apps/desktop/scripts/build.ts
@@ -11,6 +11,7 @@ async function main() {
// Copy assets.
build.copy("src/assets", "assets/");
build.copy("/apps/server/src/assets", "assets/");
+ build.triggerBuildAndCopyTo("packages/share-theme", "share-theme/assets/");
build.copy("/packages/share-theme/src/templates", "share-theme/templates/");
// Copy node modules dependencies
diff --git a/apps/edit-docs/demo/!!!meta.json b/apps/edit-docs/demo/!!!meta.json
index ce5046fb1..44b61171d 100644
--- a/apps/edit-docs/demo/!!!meta.json
+++ b/apps/edit-docs/demo/!!!meta.json
@@ -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": [
diff --git a/apps/edit-docs/demo/navigation.html b/apps/edit-docs/demo/navigation.html
index 1d4d5d57b..4d1371ac2 100644
--- a/apps/edit-docs/demo/navigation.html
+++ b/apps/edit-docs/demo/navigation.html
@@ -270,7 +270,7 @@
- Note Types
+ Note Types
Canvas
diff --git a/apps/edit-docs/demo/root/Miscellaneous/Day Note Template.html b/apps/edit-docs/demo/root/Miscellaneous/Day Note Template.html
index 8f2333bf0..a33b14490 100644
--- a/apps/edit-docs/demo/root/Miscellaneous/Day Note Template.html
+++ b/apps/edit-docs/demo/root/Miscellaneous/Day Note Template.html
@@ -14,6 +14,7 @@
☑️ Tasks
+
diff --git a/apps/edit-docs/demo/root/Trilium Demo.html b/apps/edit-docs/demo/root/Trilium Demo.html
index 206054b92..b5b6672d6 100644
--- a/apps/edit-docs/demo/root/Trilium Demo.html
+++ b/apps/edit-docs/demo/root/Trilium Demo.html
@@ -14,11 +14,10 @@
-
+
Welcome to Trilium Notes!
-
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.
If you need any help, visit triliumnotes.org or
our GitHub repository
-
Cleanup
-
Once you're finished with experimenting and want to cleanup these pages,
you can simply delete them all.
Formatting
-
Trilium supports classic formatting like italic , bold , bold and italic .
You can add links pointing to external pages or
Formatting examples .
Lists
-
Ordered:
-
First Item
@@ -56,7 +50,6 @@
Unordered:
-
Block quotes
-
Whereof one cannot speak, thereof one must be silent”
– Ludwig Wittgenstein
@@ -75,9 +67,9 @@
See also other examples like tables ,
checkbox lists, highlighting ,
+ href="Trilium%20Demo/Formatting%20examples/Checkbox%20lists.html">checkbox lists, highlighting , code blocks and
code blocks and math examples .
+ href="Trilium%20Demo/Formatting%20examples/Math.html">math examples.
+
+
diff --git a/apps/edit-docs/demo/root/Trilium Demo/Formatting examples/Code blocks.html b/apps/edit-docs/demo/root/Trilium Demo/Formatting examples/Code blocks.html
index 6827fa8af..214ef212e 100644
--- a/apps/edit-docs/demo/root/Trilium Demo/Formatting examples/Code blocks.html
+++ b/apps/edit-docs/demo/root/Trilium Demo/Formatting examples/Code blocks.html
@@ -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.
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,
diff --git a/apps/edit-docs/demo/root/Trilium Demo/Note Types.html b/apps/edit-docs/demo/root/Trilium Demo/Note Types.html
new file mode 100644
index 000000000..614d566bc
--- /dev/null
+++ b/apps/edit-docs/demo/root/Trilium Demo/Note Types.html
@@ -0,0 +1,21 @@
+
+
+