From fac31ff8be4e39113cb56842e2bdb8a8d88318e1 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 18 Oct 2025 20:07:08 +0300 Subject: [PATCH 01/36] chore(server): set up route for printing --- apps/server/src/routes/api/print.ts | 7 +++++++ apps/server/src/routes/routes.ts | 6 ++++-- 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 apps/server/src/routes/api/print.ts diff --git a/apps/server/src/routes/api/print.ts b/apps/server/src/routes/api/print.ts new file mode 100644 index 000000000..fff79a66c --- /dev/null +++ b/apps/server/src/routes/api/print.ts @@ -0,0 +1,7 @@ +import { Request } from "express"; + +export function getPrintablePage(req: Request) { + const { noteId } = req.params; + + return "Hello world: " + noteId; +} diff --git a/apps/server/src/routes/routes.ts b/apps/server/src/routes/routes.ts index f1aeb9209..b4e73cf3e 100644 --- a/apps/server/src/routes/routes.ts +++ b/apps/server/src/routes/routes.ts @@ -72,6 +72,7 @@ import etapiBackupRoute from "../etapi/backup.js"; import etapiMetricsRoute from "../etapi/metrics.js"; import apiDocsRoute from "./api_docs.js"; import { apiResultHandler, apiRoute, asyncApiRoute, asyncRoute, route, router, uploadMiddlewareWithErrorHandling } from "./route_api.js"; +import { getPrintablePage } from "./api/print.js"; const GET = "get", PST = "post", @@ -378,8 +379,6 @@ function register(app: express.Application) { asyncApiRoute(PST, "/api/llm/chat/:chatNoteId/messages", llmRoute.sendMessage); asyncApiRoute(PST, "/api/llm/chat/:chatNoteId/messages/stream", llmRoute.streamMessage); - - // LLM provider endpoints - moved under /api/llm/providers hierarchy asyncApiRoute(GET, "/api/llm/providers/ollama/models", ollamaRoute.listModels); asyncApiRoute(GET, "/api/llm/providers/openai/models", openaiRoute.listModels); @@ -388,6 +387,9 @@ function register(app: express.Application) { // API Documentation apiDocsRoute(app); + // Printing route + apiRoute(GET, "/print/:noteId", getPrintablePage); + app.use("", router); } From 63bcd80375c02433ce8c3312556c1713d5f5cc81 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 18 Oct 2025 20:15:28 +0300 Subject: [PATCH 02/36] chore(server): set up template for printing --- apps/server/src/assets/views/print.ejs | 30 ++++++++++++++++++++++++++ apps/server/src/routes/api/print.ts | 13 ++++++++--- apps/server/src/routes/routes.ts | 2 +- 3 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 apps/server/src/assets/views/print.ejs diff --git a/apps/server/src/assets/views/print.ejs b/apps/server/src/assets/views/print.ejs new file mode 100644 index 000000000..2ed13581f --- /dev/null +++ b/apps/server/src/assets/views/print.ejs @@ -0,0 +1,30 @@ + + + + + + + + + + Trilium Notes + + + + + + + + + + + + + + diff --git a/apps/server/src/routes/api/print.ts b/apps/server/src/routes/api/print.ts index fff79a66c..844769d56 100644 --- a/apps/server/src/routes/api/print.ts +++ b/apps/server/src/routes/api/print.ts @@ -1,7 +1,14 @@ -import { Request } from "express"; +import { Request, Response } from "express"; +import assetPath from "../../services/asset_path"; +import app_path from "../../services/app_path"; +import { getCurrentLocale } from "../../services/i18n"; -export function getPrintablePage(req: Request) { +export function getPrintablePage(req: Request, res: Response) { const { noteId } = req.params; - return "Hello world: " + noteId; + res.render("print", { + assetPath: assetPath, + appPath: app_path, + currentLocale: getCurrentLocale() + }); } diff --git a/apps/server/src/routes/routes.ts b/apps/server/src/routes/routes.ts index b4e73cf3e..a76810feb 100644 --- a/apps/server/src/routes/routes.ts +++ b/apps/server/src/routes/routes.ts @@ -388,7 +388,7 @@ function register(app: express.Application) { apiDocsRoute(app); // Printing route - apiRoute(GET, "/print/:noteId", getPrintablePage); + route(GET, "/print/:noteId", [ auth.checkAuth ], getPrintablePage); app.use("", router); } From e83eacb18b1e41a0ebd29f3742221a4797f9402e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 18 Oct 2025 20:23:17 +0300 Subject: [PATCH 03/36] chore(server): get printing template in order --- apps/client/src/print.ts | 1 + apps/client/vite.config.mts | 3 ++- apps/server/src/assets/views/print.ejs | 6 +++--- 3 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 apps/client/src/print.ts diff --git a/apps/client/src/print.ts b/apps/client/src/print.ts new file mode 100644 index 000000000..b03df2c43 --- /dev/null +++ b/apps/client/src/print.ts @@ -0,0 +1 @@ +console.log("Print script is here."); diff --git a/apps/client/vite.config.mts b/apps/client/vite.config.mts index 04a5b0911..3dd52b6b9 100644 --- a/apps/client/vite.config.mts +++ b/apps/client/vite.config.mts @@ -76,7 +76,8 @@ export default defineConfig(() => ({ 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") + runtime: join(__dirname, "src", "runtime.ts"), + print: join(__dirname, "src", "print.ts") }, output: { entryFileNames: "src/[name].js", diff --git a/apps/server/src/assets/views/print.ejs b/apps/server/src/assets/views/print.ejs index 2ed13581f..7e39b9567 100644 --- a/apps/server/src/assets/views/print.ejs +++ b/apps/server/src/assets/views/print.ejs @@ -6,9 +6,9 @@ - + Trilium Notes - + - + From 54724b8c5802223774add80f00afe9d49bc2d562 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 18 Oct 2025 21:01:31 +0300 Subject: [PATCH 04/36] chore(client/print): load nota into forca --- apps/client/src/print.ts | 20 +++++- apps/client/src/services/froca.ts | 7 +- apps/client/src/services/protected_session.ts | 70 +++++++++---------- apps/client/src/services/tree.ts | 18 ++--- .../assets/views/partials/windowGlobal.ejs | 2 +- apps/server/src/assets/views/print.ejs | 2 + apps/server/src/routes/api/print.ts | 14 ---- apps/server/src/routes/index.ts | 20 ++++-- apps/server/src/routes/routes.ts | 5 +- 9 files changed, 88 insertions(+), 70 deletions(-) delete mode 100644 apps/server/src/routes/api/print.ts diff --git a/apps/client/src/print.ts b/apps/client/src/print.ts index b03df2c43..3fb238674 100644 --- a/apps/client/src/print.ts +++ b/apps/client/src/print.ts @@ -1 +1,19 @@ -console.log("Print script is here."); +import FNote from "./entities/fnote"; + +async function main() { + const noteId = window.location.pathname.split("/")[2]; + const froca = (await import("./services/froca")).default; + const note = await froca.getNote(noteId); + + if (!note) return; + + if (note.type === "book") { + handleCollection(note); + } +} + +function handleCollection(note: FNote) { + console.log("Rendering collection."); +} + +main(); diff --git a/apps/client/src/services/froca.ts b/apps/client/src/services/froca.ts index 6bbc3a50d..a1529db72 100644 --- a/apps/client/src/services/froca.ts +++ b/apps/client/src/services/froca.ts @@ -40,20 +40,23 @@ class FrocaImpl implements Froca { constructor() { this.initializedPromise = this.loadInitialTree(); + this.#clear(); } async loadInitialTree() { const resp = await server.get("tree"); // clear the cache only directly before adding new content which is important for e.g., switching to protected session + this.#clear(); + this.addResp(resp); + } + #clear() { this.notes = {}; this.branches = {}; this.attributes = {}; this.attachments = {}; this.blobPromises = {}; - - this.addResp(resp); } async loadSubTree(subTreeNoteId: string) { diff --git a/apps/client/src/services/protected_session.ts b/apps/client/src/services/protected_session.ts index 1e1984ae5..a2f04a9f0 100644 --- a/apps/client/src/services/protected_session.ts +++ b/apps/client/src/services/protected_session.ts @@ -70,26 +70,26 @@ async function setupProtectedSession(password: string) { protectedSessionHolder.enableProtectedSession(); } -ws.subscribeToMessages(async (message) => { - if (message.type === "protectedSessionLogin") { - await reloadData(); +// ws.subscribeToMessages(async (message) => { +// if (message.type === "protectedSessionLogin") { +// await reloadData(); - await appContext.triggerEvent("frocaReloaded", {}); +// await appContext.triggerEvent("frocaReloaded", {}); - appContext.triggerEvent("protectedSessionStarted", {}); +// appContext.triggerEvent("protectedSessionStarted", {}); - appContext.triggerCommand("closeProtectedSessionPasswordDialog"); +// appContext.triggerCommand("closeProtectedSessionPasswordDialog"); - if (protectedSessionDeferred !== null) { - protectedSessionDeferred.resolve(true); - protectedSessionDeferred = null; - } +// if (protectedSessionDeferred !== null) { +// protectedSessionDeferred.resolve(true); +// protectedSessionDeferred = null; +// } - toastService.showMessage(t("protected_session.started")); - } else if (message.type === "protectedSessionLogout") { - utils.reloadFrontendApp(`Protected session logout`); - } -}); +// toastService.showMessage(t("protected_session.started")); +// } else if (message.type === "protectedSessionLogout") { +// utils.reloadFrontendApp(`Protected session logout`); +// } +// }); async function protectNote(noteId: string, protect: boolean, includingSubtree: boolean) { await enterProtectedSession(); @@ -106,29 +106,29 @@ function makeToast(message: Message, title: string, text: string): ToastOptions }; } -ws.subscribeToMessages(async (message) => { - if (!("taskType" in message) || message.taskType !== "protectNotes") { - return; - } +// ws.subscribeToMessages(async (message) => { +// if (!("taskType" in message) || message.taskType !== "protectNotes") { +// return; +// } - const isProtecting = message.data?.protect; - const title = isProtecting ? t("protected_session.protecting-title") : t("protected_session.unprotecting-title"); +// const isProtecting = message.data?.protect; +// const title = isProtecting ? t("protected_session.protecting-title") : t("protected_session.unprotecting-title"); - if (message.type === "taskError") { - toastService.closePersistent(message.taskId); - toastService.showError(message.message); - } else if (message.type === "taskProgressCount") { - const count = message.progressCount; - const text = isProtecting ? t("protected_session.protecting-in-progress", { count }) : t("protected_session.unprotecting-in-progress-count", { count }); - toastService.showPersistent(makeToast(message, title, text)); - } else if (message.type === "taskSucceeded") { - const text = isProtecting ? t("protected_session.protecting-finished-successfully") : t("protected_session.unprotecting-finished-successfully"); - const toast = makeToast(message, title, text); - toast.closeAfter = 3000; +// if (message.type === "taskError") { +// toastService.closePersistent(message.taskId); +// toastService.showError(message.message); +// } else if (message.type === "taskProgressCount") { +// const count = message.progressCount; +// const text = isProtecting ? t("protected_session.protecting-in-progress", { count }) : t("protected_session.unprotecting-in-progress-count", { count }); +// toastService.showPersistent(makeToast(message, title, text)); +// } else if (message.type === "taskSucceeded") { +// const text = isProtecting ? t("protected_session.protecting-finished-successfully") : t("protected_session.unprotecting-finished-successfully"); +// const toast = makeToast(message, title, text); +// toast.closeAfter = 3000; - toastService.showPersistent(toast); - } -}); +// toastService.showPersistent(toast); +// } +// }); export default { protectNote, diff --git a/apps/client/src/services/tree.ts b/apps/client/src/services/tree.ts index c508654f5..75715a377 100644 --- a/apps/client/src/services/tree.ts +++ b/apps/client/src/services/tree.ts @@ -122,17 +122,17 @@ async function resolveNotePathToSegments(notePath: string, hoistedNoteId = "root } } -ws.subscribeToMessages((message) => { - if (message.type === "openNote") { - appContext.tabManager.activateOrOpenNote(message.noteId); +// ws.subscribeToMessages((message) => { +// if (message.type === "openNote") { +// appContext.tabManager.activateOrOpenNote(message.noteId); - if (utils.isElectron()) { - const currentWindow = utils.dynamicRequire("@electron/remote").getCurrentWindow(); +// if (utils.isElectron()) { +// const currentWindow = utils.dynamicRequire("@electron/remote").getCurrentWindow(); - currentWindow.show(); - } - } -}); +// currentWindow.show(); +// } +// } +// }); function getParentProtectedStatus(node: Fancytree.FancytreeNode) { return hoistedNoteService.isHoistedNode(node) ? false : node.getParent().data.isProtected; diff --git a/apps/server/src/assets/views/partials/windowGlobal.ejs b/apps/server/src/assets/views/partials/windowGlobal.ejs index 3536d5265..c69351e1a 100644 --- a/apps/server/src/assets/views/partials/windowGlobal.ejs +++ b/apps/server/src/assets/views/partials/windowGlobal.ejs @@ -3,7 +3,7 @@ window.glob = { device: "<%= device %>", - baseApiUrl: 'api/', + baseApiUrl: "<%= baseApiUrl %>", activeDialog: null, maxEntityChangeIdAtLoad: <%= maxEntityChangeIdAtLoad %>, maxEntityChangeSyncIdAtLoad: <%= maxEntityChangeSyncIdAtLoad %>, diff --git a/apps/server/src/assets/views/print.ejs b/apps/server/src/assets/views/print.ejs index 7e39b9567..c1808e507 100644 --- a/apps/server/src/assets/views/print.ejs +++ b/apps/server/src/assets/views/print.ejs @@ -21,6 +21,8 @@ document.getElementsByTagName("body")[0].style.display = "none"; +<%- include("./partials/windowGlobal.ejs", locals) %> + diff --git a/apps/server/src/routes/api/print.ts b/apps/server/src/routes/api/print.ts deleted file mode 100644 index 844769d56..000000000 --- a/apps/server/src/routes/api/print.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Request, Response } from "express"; -import assetPath from "../../services/asset_path"; -import app_path from "../../services/app_path"; -import { getCurrentLocale } from "../../services/i18n"; - -export function getPrintablePage(req: Request, res: Response) { - const { noteId } = req.params; - - res.render("print", { - assetPath: assetPath, - appPath: app_path, - currentLocale: getCurrentLocale() - }); -} diff --git a/apps/server/src/routes/index.ts b/apps/server/src/routes/index.ts index 6bede94d7..0d968ce09 100644 --- a/apps/server/src/routes/index.ts +++ b/apps/server/src/routes/index.ts @@ -16,9 +16,19 @@ import type { Request, Response } from "express"; import type BNote from "../becca/entities/bnote.js"; import { getCurrentLocale } from "../services/i18n.js"; +type View = "desktop" | "mobile" | "print"; + function index(req: Request, res: Response) { - const options = optionService.getOptionMap(); const view = getView(req); + renderView(req, res, view); +} + +export function printIndex(req: Request, res: Response) { + renderView(req, res, "print"); +} + +function renderView(req: Request, res: Response, view: View) { + const options = optionService.getOptionMap(); //'overwrite' set to false (default) => the existing token will be re-used and validated //'validateOnReuse' set to false => if validation fails, generate a new token instead of throwing an error @@ -57,8 +67,9 @@ function index(req: Request, res: Response) { isProtectedSessionAvailable: protectedSessionService.isProtectedSessionAvailable(), maxContentWidth: Math.max(640, parseInt(options.maxContentWidth)), triliumVersion: packageJson.version, - assetPath: assetPath, - appPath: appPath, + assetPath: view !== "print" ? assetPath : "../" + assetPath, + appPath: view !== "print" ? appPath : "../" + appPath, + baseApiUrl: view !== "print" ? 'api/' : "../api/", currentLocale: getCurrentLocale() }); } @@ -122,5 +133,6 @@ function getAppCssNoteIds() { } export default { - index + index, + printIndex }; diff --git a/apps/server/src/routes/routes.ts b/apps/server/src/routes/routes.ts index a76810feb..30758b36e 100644 --- a/apps/server/src/routes/routes.ts +++ b/apps/server/src/routes/routes.ts @@ -72,7 +72,6 @@ import etapiBackupRoute from "../etapi/backup.js"; import etapiMetricsRoute from "../etapi/metrics.js"; import apiDocsRoute from "./api_docs.js"; import { apiResultHandler, apiRoute, asyncApiRoute, asyncRoute, route, router, uploadMiddlewareWithErrorHandling } from "./route_api.js"; -import { getPrintablePage } from "./api/print.js"; const GET = "get", PST = "post", @@ -82,6 +81,7 @@ const GET = "get", function register(app: express.Application) { route(GET, "/", [auth.checkAuth, csrfMiddleware], indexRoute.index); + route(GET, "/print/:noteId", [ auth.checkAuth ], indexRoute.printIndex); route(GET, "/login", [auth.checkAppInitialized, auth.checkPasswordSet], loginRoute.loginPage); route(GET, "/set-password", [auth.checkAppInitialized, auth.checkPasswordNotSet], loginRoute.setPasswordPage); @@ -387,9 +387,6 @@ function register(app: express.Application) { // API Documentation apiDocsRoute(app); - // Printing route - route(GET, "/print/:noteId", [ auth.checkAuth ], getPrintablePage); - app.use("", router); } From 62855f4bb17cd4930fc6e225ee1b91eba73ebb19 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 18 Oct 2025 21:09:39 +0300 Subject: [PATCH 05/36] chore(client/print): render title using preact --- apps/client/src/{print.ts => print.tsx} | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) rename apps/client/src/{print.ts => print.tsx} (59%) diff --git a/apps/client/src/print.ts b/apps/client/src/print.tsx similarity index 59% rename from apps/client/src/print.ts rename to apps/client/src/print.tsx index 3fb238674..73d44d367 100644 --- a/apps/client/src/print.ts +++ b/apps/client/src/print.tsx @@ -1,4 +1,6 @@ +import { JSX } from "preact/jsx-runtime"; import FNote from "./entities/fnote"; +import { render } from "preact"; async function main() { const noteId = window.location.pathname.split("/")[2]; @@ -7,13 +9,18 @@ async function main() { if (!note) return; + let el: JSX.Element | null = null; if (note.type === "book") { - handleCollection(note); + el = handleCollection(note); } + + render(el, document.body); } function handleCollection(note: FNote) { - console.log("Rendering collection."); + return ( +

{note.title}

+ ); } main(); From e416caffe3645bb3d0fbb48040f8f2365a455196 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 18 Oct 2025 21:19:53 +0300 Subject: [PATCH 06/36] chore(client/print): wire printing for collections --- apps/client/src/print.tsx | 26 ++++++++++++++++--- .../src/widgets/collections/NoteList.tsx | 19 ++++++++++++-- .../collections/presentation/print.tsx | 5 ++++ 3 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 apps/client/src/widgets/collections/presentation/print.tsx diff --git a/apps/client/src/print.tsx b/apps/client/src/print.tsx index 73d44d367..267e2fdc4 100644 --- a/apps/client/src/print.tsx +++ b/apps/client/src/print.tsx @@ -1,6 +1,8 @@ import { JSX } from "preact/jsx-runtime"; import FNote from "./entities/fnote"; import { render } from "preact"; +import { getComponentByViewTypeForPrint, useNoteIds, useViewModeConfig } from "./widgets/collections/NoteList"; +import { ViewTypeOptions } from "./widgets/collections/interface"; async function main() { const noteId = window.location.pathname.split("/")[2]; @@ -11,15 +13,33 @@ async function main() { let el: JSX.Element | null = null; if (note.type === "book") { - el = handleCollection(note); + el = ; } render(el, document.body); } -function handleCollection(note: FNote) { +function Collection({ note }: { note: FNote }) { + const viewType = note.getLabelValue("viewType") as ViewTypeOptions ?? "grid"; + const viewConfig = useViewModeConfig(note, viewType); + const noteIds = useNoteIds(note, viewType, "print"); + const component = getComponentByViewTypeForPrint(viewType, { + saveConfig() { + // While printing we don't allow for interactivity, so saving the config is a no-op. + }, + viewConfig: viewConfig?.[0] ?? {}, + note, + notePath: note.getBestNotePath().join("/"), + noteIds, + highlightedTokens: null + }); + return ( -

{note.title}

+ <> +

{note.title}

+ + {component} + ); } diff --git a/apps/client/src/widgets/collections/NoteList.tsx b/apps/client/src/widgets/collections/NoteList.tsx index cb43ab1be..d0ee5ed07 100644 --- a/apps/client/src/widgets/collections/NoteList.tsx +++ b/apps/client/src/widgets/collections/NoteList.tsx @@ -13,6 +13,7 @@ import { subscribeToMessages, unsubscribeToMessage as unsubscribeFromMessage } f import { WebSocketMessage } from "@triliumnext/commons"; import froca from "../../services/froca"; import PresentationView from "./presentation"; +import PresentationPrintView from "./presentation/print"; interface NoteListProps { note: FNote | null | undefined; @@ -110,6 +111,20 @@ function getComponentByViewType(viewType: ViewTypeOptions, props: ViewModeProps< } } +export function getComponentByViewTypeForPrint(viewType: ViewTypeOptions, props: ViewModeProps) { + switch (viewType) { + case "list": + case "grid": + case "geoMap": + case "calendar": + case "table": + case "board": + return null; + case "presentation": + return + } +} + function useNoteViewType(note?: FNote | null): ViewTypeOptions | undefined { const [ viewType ] = useNoteLabel(note, "viewType"); @@ -123,7 +138,7 @@ function useNoteViewType(note?: FNote | null): ViewTypeOptions | undefined { } } -function useNoteIds(note: FNote | null | undefined, viewType: ViewTypeOptions | undefined, ntxId: string | null | undefined) { +export function useNoteIds(note: FNote | null | undefined, viewType: ViewTypeOptions | undefined, ntxId: string | null | undefined) { const [ noteIds, setNoteIds ] = useState([]); const [ includeArchived ] = useNoteLabelBoolean(note, "includeArchived"); @@ -187,7 +202,7 @@ function useNoteIds(note: FNote | null | undefined, viewType: ViewTypeOptions | return noteIds; } -function useViewModeConfig(note: FNote | null | undefined, viewType: ViewTypeOptions | undefined) { +export function useViewModeConfig(note: FNote | null | undefined, viewType: ViewTypeOptions | undefined) { const [ viewConfig, setViewConfig ] = useState<[T | undefined, (data: T) => void]>(); useEffect(() => { diff --git a/apps/client/src/widgets/collections/presentation/print.tsx b/apps/client/src/widgets/collections/presentation/print.tsx new file mode 100644 index 000000000..c2dadeb6f --- /dev/null +++ b/apps/client/src/widgets/collections/presentation/print.tsx @@ -0,0 +1,5 @@ +import { ViewModeProps } from "../interface"; + +export default function PresentationPrintView(props: ViewModeProps) { + return

Hello world.

+} From e374b31a1ca7b90fa7610a69c0d83a1c939c2bc6 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 18 Oct 2025 21:35:19 +0300 Subject: [PATCH 07/36] feat(client/print): get collections to render --- apps/client/src/print.css | 5 +++ apps/client/src/print.tsx | 41 ++++++++----------- .../src/widgets/collections/NoteList.tsx | 17 +------- .../collections/presentation/print.tsx | 5 --- 4 files changed, 23 insertions(+), 45 deletions(-) create mode 100644 apps/client/src/print.css delete mode 100644 apps/client/src/widgets/collections/presentation/print.tsx diff --git a/apps/client/src/print.css b/apps/client/src/print.css new file mode 100644 index 000000000..4927e5587 --- /dev/null +++ b/apps/client/src/print.css @@ -0,0 +1,5 @@ +html, +body { + width: 100%; + height: 100%; +} \ No newline at end of file diff --git a/apps/client/src/print.tsx b/apps/client/src/print.tsx index 267e2fdc4..ed3d83c6a 100644 --- a/apps/client/src/print.tsx +++ b/apps/client/src/print.tsx @@ -1,8 +1,8 @@ import { JSX } from "preact/jsx-runtime"; import FNote from "./entities/fnote"; import { render } from "preact"; -import { getComponentByViewTypeForPrint, useNoteIds, useViewModeConfig } from "./widgets/collections/NoteList"; -import { ViewTypeOptions } from "./widgets/collections/interface"; +import { CustomNoteList } from "./widgets/collections/NoteList"; +import "./print.css"; async function main() { const noteId = window.location.pathname.split("/")[2]; @@ -13,33 +13,26 @@ async function main() { let el: JSX.Element | null = null; if (note.type === "book") { - el = ; + el = handleCollection(note); } - render(el, document.body); -} - -function Collection({ note }: { note: FNote }) { - const viewType = note.getLabelValue("viewType") as ViewTypeOptions ?? "grid"; - const viewConfig = useViewModeConfig(note, viewType); - const noteIds = useNoteIds(note, viewType, "print"); - const component = getComponentByViewTypeForPrint(viewType, { - saveConfig() { - // While printing we don't allow for interactivity, so saving the config is a no-op. - }, - viewConfig: viewConfig?.[0] ?? {}, - note, - notePath: note.getBestNotePath().join("/"), - noteIds, - highlightedTokens: null - }); - - return ( + render(( <>

{note.title}

- - {component} + {el} + ), document.body); +} + +function handleCollection(note: FNote) { + return ( + ); } diff --git a/apps/client/src/widgets/collections/NoteList.tsx b/apps/client/src/widgets/collections/NoteList.tsx index d0ee5ed07..ba7d21d0c 100644 --- a/apps/client/src/widgets/collections/NoteList.tsx +++ b/apps/client/src/widgets/collections/NoteList.tsx @@ -13,7 +13,6 @@ import { subscribeToMessages, unsubscribeToMessage as unsubscribeFromMessage } f import { WebSocketMessage } from "@triliumnext/commons"; import froca from "../../services/froca"; import PresentationView from "./presentation"; -import PresentationPrintView from "./presentation/print"; interface NoteListProps { note: FNote | null | undefined; @@ -35,7 +34,7 @@ export function SearchNoteList(props: Omit } -function CustomNoteList({ note, isEnabled: shouldEnable, notePath, highlightedTokens, displayOnlyCollections, ntxId }: NoteListProps) { +export function CustomNoteList({ note, isEnabled: shouldEnable, notePath, highlightedTokens, displayOnlyCollections, ntxId }: NoteListProps) { const widgetRef = useRef(null); const viewType = useNoteViewType(note); const noteIds = useNoteIds(note, viewType, ntxId); @@ -111,20 +110,6 @@ function getComponentByViewType(viewType: ViewTypeOptions, props: ViewModeProps< } } -export function getComponentByViewTypeForPrint(viewType: ViewTypeOptions, props: ViewModeProps) { - switch (viewType) { - case "list": - case "grid": - case "geoMap": - case "calendar": - case "table": - case "board": - return null; - case "presentation": - return - } -} - function useNoteViewType(note?: FNote | null): ViewTypeOptions | undefined { const [ viewType ] = useNoteLabel(note, "viewType"); diff --git a/apps/client/src/widgets/collections/presentation/print.tsx b/apps/client/src/widgets/collections/presentation/print.tsx deleted file mode 100644 index c2dadeb6f..000000000 --- a/apps/client/src/widgets/collections/presentation/print.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { ViewModeProps } from "../interface"; - -export default function PresentationPrintView(props: ViewModeProps) { - return

Hello world.

-} From f6d7ecab40451eb894fe98c8e0d61943b71bb5b7 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 18 Oct 2025 21:56:10 +0300 Subject: [PATCH 08/36] feat(client/print): render presentation without shadow DOM --- apps/client/src/layouts/desktop_layout.tsx | 2 +- apps/client/src/layouts/layout_commons.tsx | 2 +- apps/client/src/layouts/mobile_layout.tsx | 2 +- apps/client/src/print.tsx | 1 + .../src/widgets/collections/NoteList.tsx | 10 +++--- .../src/widgets/collections/interface.ts | 3 ++ .../collections/presentation/index.tsx | 33 ++++++++++++------- apps/client/src/widgets/search_result.tsx | 1 + 8 files changed, 36 insertions(+), 18 deletions(-) diff --git a/apps/client/src/layouts/desktop_layout.tsx b/apps/client/src/layouts/desktop_layout.tsx index 9dc4b76ee..3f9416584 100644 --- a/apps/client/src/layouts/desktop_layout.tsx +++ b/apps/client/src/layouts/desktop_layout.tsx @@ -138,7 +138,7 @@ export default class DesktopLayout { .child(new PromotedAttributesWidget()) .child() .child(new NoteDetailWidget()) - .child() + .child() .child() .child() .child() diff --git a/apps/client/src/layouts/layout_commons.tsx b/apps/client/src/layouts/layout_commons.tsx index 292006011..610f31dda 100644 --- a/apps/client/src/layouts/layout_commons.tsx +++ b/apps/client/src/layouts/layout_commons.tsx @@ -66,6 +66,6 @@ export function applyModals(rootContainer: RootContainer) { .child() .child(new PromotedAttributesWidget()) .child(new NoteDetailWidget()) - .child()) + .child()) .child(); } diff --git a/apps/client/src/layouts/mobile_layout.tsx b/apps/client/src/layouts/mobile_layout.tsx index 3d21b9405..b952c2d0b 100644 --- a/apps/client/src/layouts/mobile_layout.tsx +++ b/apps/client/src/layouts/mobile_layout.tsx @@ -154,7 +154,7 @@ export default class MobileLayout { .filling() .contentSized() .child(new NoteDetailWidget()) - .child() + .child() .child() ) .child() diff --git a/apps/client/src/print.tsx b/apps/client/src/print.tsx index ed3d83c6a..5bfd0d5f1 100644 --- a/apps/client/src/print.tsx +++ b/apps/client/src/print.tsx @@ -32,6 +32,7 @@ function handleCollection(note: FNote) { notePath={note.getBestNotePath().join("/")} ntxId="print" highlightedTokens={null} + media="print" /> ); } diff --git a/apps/client/src/widgets/collections/NoteList.tsx b/apps/client/src/widgets/collections/NoteList.tsx index ba7d21d0c..11abcc5c2 100644 --- a/apps/client/src/widgets/collections/NoteList.tsx +++ b/apps/client/src/widgets/collections/NoteList.tsx @@ -1,4 +1,4 @@ -import { allViewTypes, ViewModeProps, ViewTypeOptions } from "./interface"; +import { allViewTypes, ViewModeMedia, ViewModeProps, ViewTypeOptions } from "./interface"; import { useNoteContext, useNoteLabel, useNoteLabelBoolean, useTriliumEvent } from "../react/hooks"; import FNote from "../../entities/fnote"; import "./NoteList.css"; @@ -22,9 +22,10 @@ interface NoteListProps { displayOnlyCollections?: boolean; isEnabled: boolean; ntxId: string | null | undefined; + media: ViewModeMedia; } -export default function NoteList(props: Pick) { +export default function NoteList(props: Pick) { const { note, noteContext, notePath, ntxId } = useNoteContext(); const isEnabled = noteContext?.hasNoteList(); return @@ -34,7 +35,7 @@ export function SearchNoteList(props: Omit } -export function CustomNoteList({ note, isEnabled: shouldEnable, notePath, highlightedTokens, displayOnlyCollections, ntxId }: NoteListProps) { +export function CustomNoteList({ note, isEnabled: shouldEnable, notePath, highlightedTokens, displayOnlyCollections, ntxId, ...restProps }: NoteListProps) { const widgetRef = useRef(null); const viewType = useNoteViewType(note); const noteIds = useNoteIds(note, viewType, ntxId); @@ -76,7 +77,8 @@ export function CustomNoteList({ note, isEnabled: shouldEnable note, noteIds, notePath, highlightedTokens, viewConfig: viewModeConfig[0], - saveConfig: viewModeConfig[1] + saveConfig: viewModeConfig[1], + ...restProps } } diff --git a/apps/client/src/widgets/collections/interface.ts b/apps/client/src/widgets/collections/interface.ts index 91b9f301b..81a8e4d3d 100644 --- a/apps/client/src/widgets/collections/interface.ts +++ b/apps/client/src/widgets/collections/interface.ts @@ -3,6 +3,8 @@ import FNote from "../../entities/fnote"; export const allViewTypes = ["list", "grid", "calendar", "table", "geoMap", "board", "presentation"] as const; export type ViewTypeOptions = typeof allViewTypes[number]; +export type ViewModeMedia = "screen" | "print"; + export interface ViewModeProps { note: FNote; notePath: string; @@ -13,4 +15,5 @@ export interface ViewModeProps { highlightedTokens: string[] | null | undefined; viewConfig: T | undefined; saveConfig(newConfig: T): void; + media: ViewModeMedia; } diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index 6ed58c796..297b7f0e5 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -14,7 +14,7 @@ import { t } from "../../../services/i18n"; import { DEFAULT_THEME, loadPresentationTheme } from "./themes"; import FNote from "../../../entities/fnote"; -export default function PresentationView({ note, noteIds }: ViewModeProps<{}>) { +export default function PresentationView({ note, noteIds, media }: ViewModeProps<{}>) { const [ presentation, setPresentation ] = useState(); const containerRef = useRef(null); const [ api, setApi ] = useState(); @@ -33,18 +33,28 @@ export default function PresentationView({ note, noteIds }: ViewModeProps<{}>) { useLayoutEffect(refresh, [ note, noteIds ]); - return presentation && stylesheets && ( + if (!presentation || !stylesheets) return; + const content = ( <> - - {stylesheets.map(stylesheet => )} - - - + {stylesheets.map(stylesheet => )} + - ) + ); + + if (media === "screen") { + return ( + <> + {content} + + + ) + } else { + // Shadow DOM doesn't work well with Reveal.js's PDF printing mechanism. + return content; + } } function usePresentationStylesheets(note: FNote) { @@ -128,6 +138,7 @@ function Presentation({ presentation, setApi } : { presentation: PresentationMod const api = new Reveal(containerRef.current, { transition: "slide", embedded: true, + pdfMaxPagesPerSlide: 1, keyboardCondition(event) { // Full-screen requests sometimes fail, we rely on the UI button instead. if (event.key === "f") { diff --git a/apps/client/src/widgets/search_result.tsx b/apps/client/src/widgets/search_result.tsx index 6c1c07419..fd3c24ab4 100644 --- a/apps/client/src/widgets/search_result.tsx +++ b/apps/client/src/widgets/search_result.tsx @@ -54,6 +54,7 @@ export default function SearchResult() { {state === SearchResultState.GOT_RESULTS && ( Date: Sat, 18 Oct 2025 21:59:09 +0300 Subject: [PATCH 09/36] fix(client/print): slides not paginating correctly --- apps/client/src/print.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/client/src/print.css b/apps/client/src/print.css index 4927e5587..5d31138ce 100644 --- a/apps/client/src/print.css +++ b/apps/client/src/print.css @@ -2,4 +2,9 @@ html, body { width: 100%; height: 100%; +} + +.note-list-widget.full-height, +.note-list-widget.full-height .note-list-widget-content { + height: unset !important; } \ No newline at end of file From 750c4104f7b545d2910356175ed50cf644c78515 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 18 Oct 2025 22:05:57 +0300 Subject: [PATCH 10/36] feat(client/print): automatically apply right query param --- apps/client/src/widgets/collections/presentation/index.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index 297b7f0e5..82d0a47cd 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -51,7 +51,12 @@ export default function PresentationView({ note, noteIds, media }: ViewModeProps ) - } else { + } else if (media === "print") { + // Printing needs a query parameter that is read by Reveal.js. + const url = new URL(window.location.href); + url.searchParams.set("print-pdf", ""); + window.history.replaceState({}, '', url); + // Shadow DOM doesn't work well with Reveal.js's PDF printing mechanism. return content; } From df176c4e4ab6fb495f0b2843f734d3c8c5ded305 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 18 Oct 2025 22:11:33 +0300 Subject: [PATCH 11/36] fix(client/print): title interfering with presentation --- apps/client/src/print.tsx | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/apps/client/src/print.tsx b/apps/client/src/print.tsx index 5bfd0d5f1..dda6ae2b2 100644 --- a/apps/client/src/print.tsx +++ b/apps/client/src/print.tsx @@ -1,4 +1,3 @@ -import { JSX } from "preact/jsx-runtime"; import FNote from "./entities/fnote"; import { render } from "preact"; import { CustomNoteList } from "./widgets/collections/NoteList"; @@ -10,31 +9,26 @@ async function main() { const note = await froca.getNote(noteId); if (!note) return; - - let el: JSX.Element | null = null; - if (note.type === "book") { - el = handleCollection(note); - } - - render(( - <> -

{note.title}

- {el} - - ), document.body); + render(getElementForNote(note), document.body); } -function handleCollection(note: FNote) { - return ( - - ); + />; + } + + // Other note types. + return <> +

{note.title}

+ ; } main(); From b0234a75f8c5e2d7639519e7d99bb789bfccf9b2 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 18 Oct 2025 22:25:50 +0300 Subject: [PATCH 12/36] fix(client/print): variables not loading for printing presentations --- .../widgets/collections/presentation/index.tsx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index 82d0a47cd..44341688a 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -1,4 +1,4 @@ -import { ViewModeProps } from "../interface"; +import { ViewModeMedia, ViewModeProps } from "../interface"; import { useEffect, useLayoutEffect, useRef, useState } from "preact/hooks"; import Reveal from "reveal.js"; import slideBaseStylesheet from "reveal.js/dist/reveal.css?raw"; @@ -18,7 +18,7 @@ export default function PresentationView({ note, noteIds, media }: ViewModeProps const [ presentation, setPresentation ] = useState(); const containerRef = useRef(null); const [ api, setApi ] = useState(); - const stylesheets = usePresentationStylesheets(note); + const stylesheets = usePresentationStylesheets(note, media); function refresh() { buildPresentationModel(note).then(setPresentation); @@ -62,17 +62,22 @@ export default function PresentationView({ note, noteIds, media }: ViewModeProps } } -function usePresentationStylesheets(note: FNote) { +function usePresentationStylesheets(note: FNote, media: ViewModeMedia) { const [ themeName ] = useNoteLabelWithDefault(note, "presentation:theme", DEFAULT_THEME); const [ stylesheets, setStylesheets ] = useState(); useLayoutEffect(() => { loadPresentationTheme(themeName).then((themeStylesheet) => { - setStylesheets([ + let stylesheets = [ slideBaseStylesheet, themeStylesheet, slideCustomStylesheet - ].map(stylesheet => stylesheet.replace(/:root/g, ":host"))); + ]; + if (media === "screen") { + // We are rendering in the shadow DOM, so the global variables are not set correctly. + stylesheets = stylesheets.map(stylesheet => stylesheet.replace(/:root/g, ":host")); + } + setStylesheets(stylesheets); }); }, [ themeName ]); From 49cd8b2a240be56c3d6a5af6d23c00be786a0e74 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 18 Oct 2025 22:54:29 +0300 Subject: [PATCH 13/36] chore(client/print): use different approach than custom route (WIP) --- apps/server/src/routes/index.ts | 26 ++++++++++++-------------- apps/server/src/routes/routes.ts | 1 - 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/apps/server/src/routes/index.ts b/apps/server/src/routes/index.ts index 0d968ce09..d5183cbbe 100644 --- a/apps/server/src/routes/index.ts +++ b/apps/server/src/routes/index.ts @@ -20,14 +20,7 @@ type View = "desktop" | "mobile" | "print"; function index(req: Request, res: Response) { const view = getView(req); - renderView(req, res, view); -} - -export function printIndex(req: Request, res: Response) { - renderView(req, res, "print"); -} - -function renderView(req: Request, res: Response, view: View) { + console.log("Got view ", view); const options = optionService.getOptionMap(); //'overwrite' set to false (default) => the existing token will be re-used and validated @@ -67,14 +60,20 @@ function renderView(req: Request, res: Response, view: View) { isProtectedSessionAvailable: protectedSessionService.isProtectedSessionAvailable(), maxContentWidth: Math.max(640, parseInt(options.maxContentWidth)), triliumVersion: packageJson.version, - assetPath: view !== "print" ? assetPath : "../" + assetPath, - appPath: view !== "print" ? appPath : "../" + appPath, - baseApiUrl: view !== "print" ? 'api/' : "../api/", + assetPath, + appPath, + baseApiUrl: 'api/', currentLocale: getCurrentLocale() }); } -function getView(req: Request): "desktop" | "mobile" { +function getView(req: Request): View { + console.log("Got ", req.query); + // Special override for printing. + if ("print" in req.query) { + return "print"; + } + // Electron always uses the desktop view. if (isElectron) { return "desktop"; @@ -133,6 +132,5 @@ function getAppCssNoteIds() { } export default { - index, - printIndex + index }; diff --git a/apps/server/src/routes/routes.ts b/apps/server/src/routes/routes.ts index 30758b36e..9ba6b686c 100644 --- a/apps/server/src/routes/routes.ts +++ b/apps/server/src/routes/routes.ts @@ -81,7 +81,6 @@ const GET = "get", function register(app: express.Application) { route(GET, "/", [auth.checkAuth, csrfMiddleware], indexRoute.index); - route(GET, "/print/:noteId", [ auth.checkAuth ], indexRoute.printIndex); route(GET, "/login", [auth.checkAppInitialized, auth.checkPasswordSet], loginRoute.loginPage); route(GET, "/set-password", [auth.checkAppInitialized, auth.checkPasswordNotSet], loginRoute.setPasswordPage); From 89dac52f490fb3e5eb2507581006a4847808a557 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 19 Oct 2025 15:38:10 +0300 Subject: [PATCH 14/36] fix(client/print): read note ID properly from note path --- apps/client/src/print.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/client/src/print.tsx b/apps/client/src/print.tsx index dda6ae2b2..4d74fd90c 100644 --- a/apps/client/src/print.tsx +++ b/apps/client/src/print.tsx @@ -4,7 +4,10 @@ import { CustomNoteList } from "./widgets/collections/NoteList"; import "./print.css"; async function main() { - const noteId = window.location.pathname.split("/")[2]; + const notePath = window.location.hash.substring(1); + const noteId = notePath.split("/").at(-1); + if (!noteId) return; + const froca = (await import("./services/froca")).default; const note = await froca.getNote(noteId); From 64576458b7053c739fdbaab9ef2fb15563e0abd8 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 19 Oct 2025 16:27:13 +0300 Subject: [PATCH 15/36] feat(client/print): print presentations with waiting for slides to load --- apps/client/src/print.tsx | 21 +++++++++++++++++-- apps/client/src/stylesheets/style.css | 10 +++++++++ .../src/widgets/collections/NoteList.tsx | 3 ++- .../src/widgets/collections/interface.ts | 1 + .../collections/presentation/index.tsx | 10 ++++++++- apps/client/src/widgets/note_detail.ts | 15 +++++++++++-- .../client/src/widgets/ribbon/NoteActions.tsx | 8 +++---- 7 files changed, 58 insertions(+), 10 deletions(-) diff --git a/apps/client/src/print.tsx b/apps/client/src/print.tsx index 4d74fd90c..0ff7522f9 100644 --- a/apps/client/src/print.tsx +++ b/apps/client/src/print.tsx @@ -2,6 +2,7 @@ import FNote from "./entities/fnote"; import { render } from "preact"; import { CustomNoteList } from "./widgets/collections/NoteList"; import "./print.css"; +import { useCallback, useRef } from "preact/hooks"; async function main() { const notePath = window.location.hash.substring(1); @@ -12,10 +13,25 @@ async function main() { const note = await froca.getNote(noteId); if (!note) return; - render(getElementForNote(note), document.body); + render(, document.body); } -function getElementForNote(note: FNote) { +function App({ note }: { note: FNote }) { + return ( + <> + + + ); +} + +function ContentRenderer({ note }: { note: FNote }) { + const sentReadyEvent = useRef(false); + const onReady = useCallback(() => { + if (sentReadyEvent.current) return; + window.dispatchEvent(new Event("note-ready")); + sentReadyEvent.current = true; + }, []); + // Collections. if (note.type === "book") { return ; } diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index 03d1bd8ff..fd8383130 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -2422,4 +2422,14 @@ footer.webview-footer button { .revision-diff-removed { background: rgba(255, 100, 100, 0.5); text-decoration: line-through; +} + +iframe.print-iframe { + position: absolute; + top: 0; + left: -600px; + right: -600px; + bottom: 0; + width: 0; + height: 0; } \ No newline at end of file diff --git a/apps/client/src/widgets/collections/NoteList.tsx b/apps/client/src/widgets/collections/NoteList.tsx index 11abcc5c2..f9c9ba5d1 100644 --- a/apps/client/src/widgets/collections/NoteList.tsx +++ b/apps/client/src/widgets/collections/NoteList.tsx @@ -23,9 +23,10 @@ interface NoteListProps { isEnabled: boolean; ntxId: string | null | undefined; media: ViewModeMedia; + onReady: () => void; } -export default function NoteList(props: Pick) { +export default function NoteList(props: Pick) { const { note, noteContext, notePath, ntxId } = useNoteContext(); const isEnabled = noteContext?.hasNoteList(); return diff --git a/apps/client/src/widgets/collections/interface.ts b/apps/client/src/widgets/collections/interface.ts index 81a8e4d3d..7bec23a64 100644 --- a/apps/client/src/widgets/collections/interface.ts +++ b/apps/client/src/widgets/collections/interface.ts @@ -16,4 +16,5 @@ export interface ViewModeProps { viewConfig: T | undefined; saveConfig(newConfig: T): void; media: ViewModeMedia; + onReady(): void; } diff --git a/apps/client/src/widgets/collections/presentation/index.tsx b/apps/client/src/widgets/collections/presentation/index.tsx index 44341688a..dfa4574e0 100644 --- a/apps/client/src/widgets/collections/presentation/index.tsx +++ b/apps/client/src/widgets/collections/presentation/index.tsx @@ -14,7 +14,7 @@ import { t } from "../../../services/i18n"; import { DEFAULT_THEME, loadPresentationTheme } from "./themes"; import FNote from "../../../entities/fnote"; -export default function PresentationView({ note, noteIds, media }: ViewModeProps<{}>) { +export default function PresentationView({ note, noteIds, media, onReady }: ViewModeProps<{}>) { const [ presentation, setPresentation ] = useState(); const containerRef = useRef(null); const [ api, setApi ] = useState(); @@ -33,6 +33,14 @@ export default function PresentationView({ note, noteIds, media }: ViewModeProps useLayoutEffect(refresh, [ note, noteIds ]); + useEffect(() => { + // We need to wait for Reveal.js to initialize (by setting api) and for the presentation to become available. + if (api && presentation) { + // Timeout is necessary because it otherwise can cause flakiness by rendering only the first slide. + setTimeout(onReady, 200); + } + }, [ api, presentation ]); + if (!presentation || !stylesheets) return; const content = ( <> diff --git a/apps/client/src/widgets/note_detail.ts b/apps/client/src/widgets/note_detail.ts index 5b798a06a..0ec68060c 100644 --- a/apps/client/src/widgets/note_detail.ts +++ b/apps/client/src/widgets/note_detail.ts @@ -297,8 +297,19 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { return; } - // Trigger in timeout to dismiss the menu while printing. - setTimeout(window.print, 0); + const iframe = document.createElement('iframe'); + iframe.src = `?print#${this.notePath}`; + iframe.className = "print-iframe"; + document.body.appendChild(iframe); + iframe.onload = () => { + console.log("Got ", iframe, iframe.contentWindow); + if (iframe.contentWindow) { + iframe.contentWindow.addEventListener("note-ready", () => { + iframe.contentWindow?.print(); + document.body.removeChild(iframe); + }); + } + }; } async exportAsPdfEvent() { diff --git a/apps/client/src/widgets/ribbon/NoteActions.tsx b/apps/client/src/widgets/ribbon/NoteActions.tsx index f780eab8b..2fed2ea02 100644 --- a/apps/client/src/widgets/ribbon/NoteActions.tsx +++ b/apps/client/src/widgets/ribbon/NoteActions.tsx @@ -47,11 +47,11 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not const canBeConvertedToAttachment = note?.isEligibleForConversionToAttachment(); const isSearchable = ["text", "code", "book", "mindMap", "doc"].includes(note.type); const isInOptions = note.noteId.startsWith("_options"); - const isPrintable = ["text", "code"].includes(note.type); + const isPrintable = ["text", "code", "book"].includes(note.type); const isElectron = getIsElectron(); const isMac = getIsMac(); const hasSource = ["text", "code", "relationMap", "mermaid", "canvas", "mindMap"].includes(note.type); - const isSearchOrBook = ["search", "book"].includes(note.type); + const isSearchOrBook = ["search", "book"].includes(note.type); return ( noteContext?.notePath && parentComponent?.triggerCommand("showExportDialog", { - notePath: noteContext.notePath, + notePath: noteContext.notePath, defaultType: "single" })} /> @@ -133,4 +133,4 @@ function ConvertToAttachment({ note }: { note: FNote }) { }} >{t("note_actions.convert_into_attachment")} ) -} \ No newline at end of file +} From 76c337602bbfa3a18943ccb34ec5adbd21efd364 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 19 Oct 2025 16:35:13 +0300 Subject: [PATCH 16/36] chore(print): clean up some logs --- apps/client/src/widgets/note_detail.ts | 12 +++++------- apps/server/src/routes/index.ts | 2 -- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/apps/client/src/widgets/note_detail.ts b/apps/client/src/widgets/note_detail.ts index 0ec68060c..472c4aeea 100644 --- a/apps/client/src/widgets/note_detail.ts +++ b/apps/client/src/widgets/note_detail.ts @@ -302,13 +302,11 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { iframe.className = "print-iframe"; document.body.appendChild(iframe); iframe.onload = () => { - console.log("Got ", iframe, iframe.contentWindow); - if (iframe.contentWindow) { - iframe.contentWindow.addEventListener("note-ready", () => { - iframe.contentWindow?.print(); - document.body.removeChild(iframe); - }); - } + if (!iframe.contentWindow) return; + iframe.contentWindow.addEventListener("note-ready", () => { + iframe.contentWindow?.print(); + document.body.removeChild(iframe); + }); }; } diff --git a/apps/server/src/routes/index.ts b/apps/server/src/routes/index.ts index d5183cbbe..ea4821e3a 100644 --- a/apps/server/src/routes/index.ts +++ b/apps/server/src/routes/index.ts @@ -20,7 +20,6 @@ type View = "desktop" | "mobile" | "print"; function index(req: Request, res: Response) { const view = getView(req); - console.log("Got view ", view); const options = optionService.getOptionMap(); //'overwrite' set to false (default) => the existing token will be re-used and validated @@ -68,7 +67,6 @@ function index(req: Request, res: Response) { } function getView(req: Request): View { - console.log("Got ", req.query); // Special override for printing. if ("print" in req.query) { return "print"; From c160ab472151279f4168b8e9c82a567cf3b68110 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 19 Oct 2025 16:45:04 +0300 Subject: [PATCH 17/36] feat(client/print): don't connect to websocket --- apps/client/src/services/ws.ts | 2 ++ apps/client/src/types.d.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/client/src/services/ws.ts b/apps/client/src/services/ws.ts index 517eb677e..488000ba1 100644 --- a/apps/client/src/services/ws.ts +++ b/apps/client/src/services/ws.ts @@ -304,6 +304,8 @@ async function sendPing() { } setTimeout(() => { + if (glob.device === "print") return; + ws = connectWebSocket(); lastPingTs = Date.now(); diff --git a/apps/client/src/types.d.ts b/apps/client/src/types.d.ts index 36b56b9a6..2546d2ffa 100644 --- a/apps/client/src/types.d.ts +++ b/apps/client/src/types.d.ts @@ -16,7 +16,7 @@ interface ElectronProcess { interface CustomGlobals { isDesktop: typeof utils.isDesktop; isMobile: typeof utils.isMobile; - device: "mobile" | "desktop"; + device: "mobile" | "desktop" | "print"; getComponentByEl: typeof appContext.getComponentByEl; getHeaders: typeof server.getHeaders; getReferenceLinkTitle: (href: string) => Promise; From 61bdcf2a536b60cb20e6e5ff49625198d87ba97f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 19 Oct 2025 17:12:41 +0300 Subject: [PATCH 18/36] feat(client/print): add a toast when printing is in progress --- apps/client/src/translations/en/translation.json | 3 ++- apps/client/src/widgets/note_detail.ts | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index a6858c7d6..d09d2742f 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -1722,7 +1722,8 @@ "window-on-top": "Keep Window on Top" }, "note_detail": { - "could_not_find_typewidget": "Could not find typeWidget for type '{{type}}'" + "could_not_find_typewidget": "Could not find typeWidget for type '{{type}}'", + "printing": "Printing in progress..." }, "note_title": { "placeholder": "type note's title here..." diff --git a/apps/client/src/widgets/note_detail.ts b/apps/client/src/widgets/note_detail.ts index 472c4aeea..0c1255d39 100644 --- a/apps/client/src/widgets/note_detail.ts +++ b/apps/client/src/widgets/note_detail.ts @@ -33,6 +33,7 @@ import type { NoteType } from "../entities/fnote.js"; import type TypeWidget from "./type_widgets/type_widget.js"; import { MermaidTypeWidget } from "./type_widgets/mermaid.js"; import AiChatTypeWidget from "./type_widgets/ai_chat.js"; +import toast from "../services/toast.js"; const TPL = /*html*/`
@@ -297,6 +298,11 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { return; } + toast.showPersistent({ + icon: "bx bx-loader-circle bx-spin", + message: t("note_detail.printing"), + id: "printing" + }); const iframe = document.createElement('iframe'); iframe.src = `?print#${this.notePath}`; iframe.className = "print-iframe"; @@ -304,6 +310,7 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { iframe.onload = () => { if (!iframe.contentWindow) return; iframe.contentWindow.addEventListener("note-ready", () => { + toast.closePersistent("printing"); iframe.contentWindow?.print(); document.body.removeChild(iframe); }); From fb0c3be7facb512b11d7806e04e29f34b271c4c5 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 19 Oct 2025 19:56:26 +0300 Subject: [PATCH 19/36] feat(desktop/print): integrate with offscreen rendering --- apps/client/src/widgets/note_detail.ts | 32 ++++++++++++++++---------- apps/server/src/services/window.ts | 28 ++++++++++++++++++++++ 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/apps/client/src/widgets/note_detail.ts b/apps/client/src/widgets/note_detail.ts index 0c1255d39..5310b327a 100644 --- a/apps/client/src/widgets/note_detail.ts +++ b/apps/client/src/widgets/note_detail.ts @@ -28,7 +28,7 @@ import ContentWidgetTypeWidget from "./type_widgets/content_widget.js"; import AttachmentListTypeWidget from "./type_widgets/attachment_list.js"; import AttachmentDetailTypeWidget from "./type_widgets/attachment_detail.js"; import MindMapWidget from "./type_widgets/mind_map.js"; -import utils from "../services/utils.js"; +import utils, { isElectron } from "../services/utils.js"; import type { NoteType } from "../entities/fnote.js"; import type TypeWidget from "./type_widgets/type_widget.js"; import { MermaidTypeWidget } from "./type_widgets/mermaid.js"; @@ -303,18 +303,26 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { message: t("note_detail.printing"), id: "printing" }); - const iframe = document.createElement('iframe'); - iframe.src = `?print#${this.notePath}`; - iframe.className = "print-iframe"; - document.body.appendChild(iframe); - iframe.onload = () => { - if (!iframe.contentWindow) return; - iframe.contentWindow.addEventListener("note-ready", () => { - toast.closePersistent("printing"); - iframe.contentWindow?.print(); - document.body.removeChild(iframe); + + if (isElectron()) { + const { ipcRenderer } = utils.dynamicRequire("electron"); + ipcRenderer.send("print-note", { + notePath: this.notePath }); - }; + } else { + const iframe = document.createElement('iframe'); + iframe.src = `?print#${this.notePath}`; + iframe.className = "print-iframe"; + document.body.appendChild(iframe); + iframe.onload = () => { + if (!iframe.contentWindow) return; + iframe.contentWindow.addEventListener("note-ready", () => { + toast.closePersistent("printing"); + iframe.contentWindow?.print(); + document.body.removeChild(iframe); + }); + }; + } } async exportAsPdfEvent() { diff --git a/apps/server/src/services/window.ts b/apps/server/src/services/window.ts index b26b000af..f149fd1e0 100644 --- a/apps/server/src/services/window.ts +++ b/apps/server/src/services/window.ts @@ -12,6 +12,8 @@ import type { App, BrowserWindowConstructorOptions, BrowserWindow, WebContents } import { formatDownloadTitle, isDev, isMac, isWindows } from "./utils.js"; import { t } from "i18next"; import { RESOURCE_DIR } from "./resource_dir.js"; +import { PerformanceObserverEntryList } from "perf_hooks"; +import options from "./options.js"; // Prevent the window being garbage collected let mainWindow: BrowserWindow | null; @@ -67,12 +69,38 @@ electron.ipcMain.on("create-extra-window", (event, arg) => { createExtraWindow(arg.extraWindowHash); }); +interface PrintOpts { + notePath: string; +} + interface ExportAsPdfOpts { title: string; landscape: boolean; pageSize: "A0" | "A1" | "A2" | "A3" | "A4" | "A5" | "A6" | "Legal" | "Letter" | "Tabloid" | "Ledger"; } +electron.ipcMain.on("print-note", async (e, { notePath }: PrintOpts) => { + const browserWindow = new electron.BrowserWindow({ + show: false, + webPreferences: { + nodeIntegration: true, + contextIsolation: false, + offscreen: true, + session: e.sender.session + }, + }); + await browserWindow.loadURL(`http://127.0.0.1:${port}/?print#${notePath}`); + await browserWindow.webContents.executeJavaScript(` + new Promise(resolve => { + if (window._noteReady) return resolve(); + window.addEventListener("note-ready", () => resolve()); + }); + `); + browserWindow.webContents.print({}, () => { + browserWindow.destroy(); + }); +}); + electron.ipcMain.on("export-as-pdf", async (e, opts: ExportAsPdfOpts) => { const browserWindow = electron.BrowserWindow.fromWebContents(e.sender); if (!browserWindow) { From d1854d85ce2113570d0313e4612f31b0fbd3b0e6 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 19 Oct 2025 20:23:28 +0300 Subject: [PATCH 20/36] feat(desktop/print): integrate for export to PDF --- apps/client/src/widgets/note_detail.ts | 3 +- .../src/assets/translations/en/server.json | 3 +- apps/server/src/services/window.ts | 34 ++++++++++++------- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/apps/client/src/widgets/note_detail.ts b/apps/client/src/widgets/note_detail.ts index 5310b327a..7d2791e32 100644 --- a/apps/client/src/widgets/note_detail.ts +++ b/apps/client/src/widgets/note_detail.ts @@ -326,13 +326,14 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { } async exportAsPdfEvent() { - if (!this.noteContext?.isActive() || !this.note) { + if (!this.noteContext?.isActive() || !this.note || !this.notePath) { return; } const { ipcRenderer } = utils.dynamicRequire("electron"); ipcRenderer.send("export-as-pdf", { title: this.note.title, + notePath: this.notePath, pageSize: this.note.getAttributeValue("label", "printPageSize") ?? "Letter", landscape: this.note.hasAttribute("label", "printLandscape") }); diff --git a/apps/server/src/assets/translations/en/server.json b/apps/server/src/assets/translations/en/server.json index b583bb343..972f45154 100644 --- a/apps/server/src/assets/translations/en/server.json +++ b/apps/server/src/assets/translations/en/server.json @@ -373,7 +373,8 @@ "export_filter": "PDF Document (*.pdf)", "unable-to-export-message": "The current note could not be exported as a PDF.", "unable-to-export-title": "Unable to export as PDF", - "unable-to-save-message": "The selected file could not be written to. Try again or select another destination." + "unable-to-save-message": "The selected file could not be written to. Try again or select another destination.", + "unable-to-print": "Unable to print the note" }, "tray": { "tooltip": "Trilium Notes", diff --git a/apps/server/src/services/window.ts b/apps/server/src/services/window.ts index f149fd1e0..83d1ca185 100644 --- a/apps/server/src/services/window.ts +++ b/apps/server/src/services/window.ts @@ -8,7 +8,7 @@ import sqlInit from "./sql_init.js"; import cls from "./cls.js"; import keyboardActionsService from "./keyboard_actions.js"; import electron from "electron"; -import type { App, BrowserWindowConstructorOptions, BrowserWindow, WebContents } from "electron"; +import type { App, BrowserWindowConstructorOptions, BrowserWindow, WebContents, IpcMainEvent } from "electron"; import { formatDownloadTitle, isDev, isMac, isWindows } from "./utils.js"; import { t } from "i18next"; import { RESOURCE_DIR } from "./resource_dir.js"; @@ -71,15 +71,28 @@ electron.ipcMain.on("create-extra-window", (event, arg) => { interface PrintOpts { notePath: string; + printToPdf: boolean; } interface ExportAsPdfOpts { + notePath: string; title: string; landscape: boolean; pageSize: "A0" | "A1" | "A2" | "A3" | "A4" | "A5" | "A6" | "Legal" | "Letter" | "Tabloid" | "Ledger"; } electron.ipcMain.on("print-note", async (e, { notePath }: PrintOpts) => { + const browserWindow = await getBrowserWindowForPrinting(e, notePath); + browserWindow.webContents.print({}, (success, failureReason) => { + if (success) { + browserWindow.destroy(); + } else { + electron.dialog.showErrorBox(t("pdf.unable-to-print"), failureReason); + } + }); +}); + +async function getBrowserWindowForPrinting(e: IpcMainEvent, notePath: string) { const browserWindow = new electron.BrowserWindow({ show: false, webPreferences: { @@ -96,19 +109,14 @@ electron.ipcMain.on("print-note", async (e, { notePath }: PrintOpts) => { window.addEventListener("note-ready", () => resolve()); }); `); - browserWindow.webContents.print({}, () => { - browserWindow.destroy(); - }); -}); + return browserWindow; +} -electron.ipcMain.on("export-as-pdf", async (e, opts: ExportAsPdfOpts) => { - const browserWindow = electron.BrowserWindow.fromWebContents(e.sender); - if (!browserWindow) { - return; - } +electron.ipcMain.on("export-as-pdf", async (e, { title, notePath, landscape, pageSize }: ExportAsPdfOpts) => { + const browserWindow = await getBrowserWindowForPrinting(e, notePath); const filePath = electron.dialog.showSaveDialogSync(browserWindow, { - defaultPath: formatDownloadTitle(opts.title, "file", "application/pdf"), + defaultPath: formatDownloadTitle(title, "file", "application/pdf"), filters: [ { name: t("pdf.export_filter"), @@ -123,8 +131,8 @@ electron.ipcMain.on("export-as-pdf", async (e, opts: ExportAsPdfOpts) => { let buffer: Buffer; try { buffer = await browserWindow.webContents.printToPDF({ - landscape: opts.landscape, - pageSize: opts.pageSize, + landscape, + pageSize, generateDocumentOutline: true, generateTaggedPDF: true, printBackground: true, From 3cf7e709fc41a328af7b1d80836323ce8e55f857 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 19 Oct 2025 20:50:18 +0300 Subject: [PATCH 21/36] fix(desktop/print): proper reporting when it finishes --- .../src/translations/en/translation.json | 3 +- apps/client/src/widgets/note_detail.ts | 13 +++ apps/server/src/services/window.ts | 96 ++++++++++--------- 3 files changed, 65 insertions(+), 47 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index d09d2742f..c52acb3ff 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -1723,7 +1723,8 @@ }, "note_detail": { "could_not_find_typewidget": "Could not find typeWidget for type '{{type}}'", - "printing": "Printing in progress..." + "printing": "Printing in progress...", + "printing_pdf": "Exporting to PDF in progress..." }, "note_title": { "placeholder": "type note's title here..." diff --git a/apps/client/src/widgets/note_detail.ts b/apps/client/src/widgets/note_detail.ts index 7d2791e32..a701d442b 100644 --- a/apps/client/src/widgets/note_detail.ts +++ b/apps/client/src/widgets/note_detail.ts @@ -141,6 +141,13 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { doRender() { this.$widget = $(TPL); this.contentSized(); + + if (utils.isElectron()) { + const { ipcRenderer } = utils.dynamicRequire("electron"); + ipcRenderer.on("print-done", () => { + toast.closePersistent("printing"); + }); + } } async refresh() { @@ -330,6 +337,12 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { return; } + toast.showPersistent({ + icon: "bx bx-loader-circle bx-spin", + message: t("note_detail.printing_pdf"), + id: "printing" + }); + const { ipcRenderer } = utils.dynamicRequire("electron"); ipcRenderer.send("export-as-pdf", { title: this.note.title, diff --git a/apps/server/src/services/window.ts b/apps/server/src/services/window.ts index 83d1ca185..69d273158 100644 --- a/apps/server/src/services/window.ts +++ b/apps/server/src/services/window.ts @@ -89,9 +89,59 @@ electron.ipcMain.on("print-note", async (e, { notePath }: PrintOpts) => { } else { electron.dialog.showErrorBox(t("pdf.unable-to-print"), failureReason); } + e.sender.send("print-done"); }); }); +electron.ipcMain.on("export-as-pdf", async (e, { title, notePath, landscape, pageSize }: ExportAsPdfOpts) => { + async function print() { + const browserWindow = await getBrowserWindowForPrinting(e, notePath); + + const filePath = electron.dialog.showSaveDialogSync(browserWindow, { + defaultPath: formatDownloadTitle(title, "file", "application/pdf"), + filters: [ + { + name: t("pdf.export_filter"), + extensions: ["pdf"] + } + ] + }); + if (!filePath) return; + + let buffer: Buffer; + try { + buffer = await browserWindow.webContents.printToPDF({ + landscape, + pageSize, + generateDocumentOutline: true, + generateTaggedPDF: true, + printBackground: true, + displayHeaderFooter: true, + headerTemplate: `
`, + footerTemplate: ` +
+
+ ` + }); + } catch (e) { + electron.dialog.showErrorBox(t("pdf.unable-to-export-title"), t("pdf.unable-to-export-message")); + return; + } + + try { + await fs.writeFile(filePath, buffer); + } catch (e) { + electron.dialog.showErrorBox(t("pdf.unable-to-export-title"), t("pdf.unable-to-save-message")); + return; + } + + electron.shell.openPath(filePath); + } + + await print(); + e.sender.send("print-done"); +}); + async function getBrowserWindowForPrinting(e: IpcMainEvent, notePath: string) { const browserWindow = new electron.BrowserWindow({ show: false, @@ -112,52 +162,6 @@ async function getBrowserWindowForPrinting(e: IpcMainEvent, notePath: string) { return browserWindow; } -electron.ipcMain.on("export-as-pdf", async (e, { title, notePath, landscape, pageSize }: ExportAsPdfOpts) => { - const browserWindow = await getBrowserWindowForPrinting(e, notePath); - - const filePath = electron.dialog.showSaveDialogSync(browserWindow, { - defaultPath: formatDownloadTitle(title, "file", "application/pdf"), - filters: [ - { - name: t("pdf.export_filter"), - extensions: ["pdf"] - } - ] - }); - if (!filePath) { - return; - } - - let buffer: Buffer; - try { - buffer = await browserWindow.webContents.printToPDF({ - landscape, - pageSize, - generateDocumentOutline: true, - generateTaggedPDF: true, - printBackground: true, - displayHeaderFooter: true, - headerTemplate: `
`, - footerTemplate: ` -
-
- ` - }); - } catch (e) { - electron.dialog.showErrorBox(t("pdf.unable-to-export-title"), t("pdf.unable-to-export-message")); - return; - } - - try { - await fs.writeFile(filePath, buffer); - } catch (e) { - electron.dialog.showErrorBox(t("pdf.unable-to-export-title"), t("pdf.unable-to-save-message")); - return; - } - - electron.shell.openPath(filePath); -}); - async function createMainWindow(app: App) { if ("setUserTasks" in app) { app.setUserTasks([ From 5957ce26f19d542afef03539334b4078ef9daebe Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 19 Oct 2025 21:27:34 +0300 Subject: [PATCH 22/36] feat(client/print): support most notes via content_renderer --- apps/client/src/print.tsx | 63 +++++++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 22 deletions(-) diff --git a/apps/client/src/print.tsx b/apps/client/src/print.tsx index 0ff7522f9..f8c8de8e4 100644 --- a/apps/client/src/print.tsx +++ b/apps/client/src/print.tsx @@ -2,7 +2,13 @@ import FNote from "./entities/fnote"; import { render } from "preact"; import { CustomNoteList } from "./widgets/collections/NoteList"; import "./print.css"; -import { useCallback, useRef } from "preact/hooks"; +import { useCallback, useEffect, useRef } from "preact/hooks"; +import content_renderer from "./services/content_renderer"; + +interface RendererProps { + note: FNote; + onReady: () => void; +} async function main() { const notePath = window.location.hash.substring(1); @@ -17,38 +23,51 @@ async function main() { } function App({ note }: { note: FNote }) { - return ( - <> - - - ); -} - -function ContentRenderer({ note }: { note: FNote }) { const sentReadyEvent = useRef(false); const onReady = useCallback(() => { if (sentReadyEvent.current) return; window.dispatchEvent(new Event("note-ready")); sentReadyEvent.current = true; }, []); + const props: RendererProps = { note, onReady }; - // Collections. - if (note.type === "book") { - return ; - } + return ( + <> + {note.type === "book" + ? + : + } + + ); +} + +function SingleNoteRenderer({ note, onReady }: RendererProps) { + const containerRef = useRef(null); + + useEffect(() => { + content_renderer.getRenderedContent(note, { + noChildrenList: true + }).then(({$renderedContent}) => { + containerRef.current?.replaceChildren(...$renderedContent); + }); + }, [ note ]); - // Other note types. return <>

{note.title}

+
; } +function CollectionRenderer({ note, onReady }: RendererProps) { + return ; +} + main(); From 678018585f5ce312934f0502b94bad451873739b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 19 Oct 2025 22:12:41 +0300 Subject: [PATCH 23/36] fix(client/print): get text notes to print --- apps/client/src/print.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/client/src/print.tsx b/apps/client/src/print.tsx index f8c8de8e4..96cf06132 100644 --- a/apps/client/src/print.tsx +++ b/apps/client/src/print.tsx @@ -2,7 +2,7 @@ import FNote from "./entities/fnote"; import { render } from "preact"; import { CustomNoteList } from "./widgets/collections/NoteList"; import "./print.css"; -import { useCallback, useEffect, useRef } from "preact/hooks"; +import { useCallback, useLayoutEffect, useRef } from "preact/hooks"; import content_renderer from "./services/content_renderer"; interface RendererProps { @@ -44,11 +44,12 @@ function App({ note }: { note: FNote }) { function SingleNoteRenderer({ note, onReady }: RendererProps) { const containerRef = useRef(null); - useEffect(() => { + useLayoutEffect(() => { content_renderer.getRenderedContent(note, { noChildrenList: true }).then(({$renderedContent}) => { containerRef.current?.replaceChildren(...$renderedContent); + requestAnimationFrame(onReady); }); }, [ note ]); From 1514432f7751fb2acef6cd9937aba5f510531ac2 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 20 Oct 2025 10:40:14 +0300 Subject: [PATCH 24/36] fix(client/print): circular dependency affecting ws --- apps/client/src/entities/fnote.ts | 3 +- apps/client/src/services/protected_session.ts | 70 +++++++++---------- apps/client/src/services/tree.ts | 18 ++--- 3 files changed, 45 insertions(+), 46 deletions(-) diff --git a/apps/client/src/entities/fnote.ts b/apps/client/src/entities/fnote.ts index dcb768dd7..bcb6c408e 100644 --- a/apps/client/src/entities/fnote.ts +++ b/apps/client/src/entities/fnote.ts @@ -1,6 +1,5 @@ import server from "../services/server.js"; import noteAttributeCache from "../services/note_attribute_cache.js"; -import ws from "../services/ws.js"; import protectedSessionHolder from "../services/protected_session_holder.js"; import cssClassManager from "../services/css_class_manager.js"; import type { Froca } from "../services/froca-interface.js"; @@ -586,7 +585,7 @@ export default class FNote { let childBranches = this.getChildBranches(); if (!childBranches) { - ws.logError(`No children for '${this.noteId}'. This shouldn't happen.`); + console.error(`No children for '${this.noteId}'. This shouldn't happen.`); return []; } diff --git a/apps/client/src/services/protected_session.ts b/apps/client/src/services/protected_session.ts index a2f04a9f0..1e1984ae5 100644 --- a/apps/client/src/services/protected_session.ts +++ b/apps/client/src/services/protected_session.ts @@ -70,26 +70,26 @@ async function setupProtectedSession(password: string) { protectedSessionHolder.enableProtectedSession(); } -// ws.subscribeToMessages(async (message) => { -// if (message.type === "protectedSessionLogin") { -// await reloadData(); +ws.subscribeToMessages(async (message) => { + if (message.type === "protectedSessionLogin") { + await reloadData(); -// await appContext.triggerEvent("frocaReloaded", {}); + await appContext.triggerEvent("frocaReloaded", {}); -// appContext.triggerEvent("protectedSessionStarted", {}); + appContext.triggerEvent("protectedSessionStarted", {}); -// appContext.triggerCommand("closeProtectedSessionPasswordDialog"); + appContext.triggerCommand("closeProtectedSessionPasswordDialog"); -// if (protectedSessionDeferred !== null) { -// protectedSessionDeferred.resolve(true); -// protectedSessionDeferred = null; -// } + if (protectedSessionDeferred !== null) { + protectedSessionDeferred.resolve(true); + protectedSessionDeferred = null; + } -// toastService.showMessage(t("protected_session.started")); -// } else if (message.type === "protectedSessionLogout") { -// utils.reloadFrontendApp(`Protected session logout`); -// } -// }); + toastService.showMessage(t("protected_session.started")); + } else if (message.type === "protectedSessionLogout") { + utils.reloadFrontendApp(`Protected session logout`); + } +}); async function protectNote(noteId: string, protect: boolean, includingSubtree: boolean) { await enterProtectedSession(); @@ -106,29 +106,29 @@ function makeToast(message: Message, title: string, text: string): ToastOptions }; } -// ws.subscribeToMessages(async (message) => { -// if (!("taskType" in message) || message.taskType !== "protectNotes") { -// return; -// } +ws.subscribeToMessages(async (message) => { + if (!("taskType" in message) || message.taskType !== "protectNotes") { + return; + } -// const isProtecting = message.data?.protect; -// const title = isProtecting ? t("protected_session.protecting-title") : t("protected_session.unprotecting-title"); + const isProtecting = message.data?.protect; + const title = isProtecting ? t("protected_session.protecting-title") : t("protected_session.unprotecting-title"); -// if (message.type === "taskError") { -// toastService.closePersistent(message.taskId); -// toastService.showError(message.message); -// } else if (message.type === "taskProgressCount") { -// const count = message.progressCount; -// const text = isProtecting ? t("protected_session.protecting-in-progress", { count }) : t("protected_session.unprotecting-in-progress-count", { count }); -// toastService.showPersistent(makeToast(message, title, text)); -// } else if (message.type === "taskSucceeded") { -// const text = isProtecting ? t("protected_session.protecting-finished-successfully") : t("protected_session.unprotecting-finished-successfully"); -// const toast = makeToast(message, title, text); -// toast.closeAfter = 3000; + if (message.type === "taskError") { + toastService.closePersistent(message.taskId); + toastService.showError(message.message); + } else if (message.type === "taskProgressCount") { + const count = message.progressCount; + const text = isProtecting ? t("protected_session.protecting-in-progress", { count }) : t("protected_session.unprotecting-in-progress-count", { count }); + toastService.showPersistent(makeToast(message, title, text)); + } else if (message.type === "taskSucceeded") { + const text = isProtecting ? t("protected_session.protecting-finished-successfully") : t("protected_session.unprotecting-finished-successfully"); + const toast = makeToast(message, title, text); + toast.closeAfter = 3000; -// toastService.showPersistent(toast); -// } -// }); + toastService.showPersistent(toast); + } +}); export default { protectNote, diff --git a/apps/client/src/services/tree.ts b/apps/client/src/services/tree.ts index 75715a377..c508654f5 100644 --- a/apps/client/src/services/tree.ts +++ b/apps/client/src/services/tree.ts @@ -122,17 +122,17 @@ async function resolveNotePathToSegments(notePath: string, hoistedNoteId = "root } } -// ws.subscribeToMessages((message) => { -// if (message.type === "openNote") { -// appContext.tabManager.activateOrOpenNote(message.noteId); +ws.subscribeToMessages((message) => { + if (message.type === "openNote") { + appContext.tabManager.activateOrOpenNote(message.noteId); -// if (utils.isElectron()) { -// const currentWindow = utils.dynamicRequire("@electron/remote").getCurrentWindow(); + if (utils.isElectron()) { + const currentWindow = utils.dynamicRequire("@electron/remote").getCurrentWindow(); -// currentWindow.show(); -// } -// } -// }); + currentWindow.show(); + } + } +}); function getParentProtectedStatus(node: Fancytree.FancytreeNode) { return hoistedNoteService.isHoistedNode(node) ? false : node.getParent().data.isProtected; From f0fa55715c0bdbe368368fa0882456af13aadc7f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 20 Oct 2025 10:48:55 +0300 Subject: [PATCH 25/36] fix(client/print): ckeditor stylesheet missing --- apps/client/src/print.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/client/src/print.tsx b/apps/client/src/print.tsx index 96cf06132..86d98b113 100644 --- a/apps/client/src/print.tsx +++ b/apps/client/src/print.tsx @@ -45,12 +45,15 @@ function SingleNoteRenderer({ note, onReady }: RendererProps) { const containerRef = useRef(null); useLayoutEffect(() => { - content_renderer.getRenderedContent(note, { - noChildrenList: true - }).then(({$renderedContent}) => { + async function load() { + if (note.type === "text") { + await import("@triliumnext/ckeditor5/src/theme/ck-content.css"); + } + const { $renderedContent } = await content_renderer.getRenderedContent(note, { noChildrenList: true }); containerRef.current?.replaceChildren(...$renderedContent); - requestAnimationFrame(onReady); - }); + } + + load().then(() => requestAnimationFrame(onReady)) }, [ note ]); return <> From 74c26b42da93579fc1ea16e558bd77b3402d86ad Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 20 Oct 2025 11:00:49 +0300 Subject: [PATCH 26/36] fix(client/print): syntax highlighting not loading on code notes --- apps/client/src/services/syntax_highlight.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/client/src/services/syntax_highlight.ts b/apps/client/src/services/syntax_highlight.ts index 9b5592a6b..e0ce55f42 100644 --- a/apps/client/src/services/syntax_highlight.ts +++ b/apps/client/src/services/syntax_highlight.ts @@ -76,7 +76,7 @@ export async function ensureMimeTypesForHighlighting(mimeTypeHint?: string) { // Load theme. const currentThemeName = String(options.get("codeBlockTheme")); - loadHighlightingTheme(currentThemeName); + await loadHighlightingTheme(currentThemeName); // Load mime types. let mimeTypes: MimeType[]; @@ -98,7 +98,7 @@ export async function ensureMimeTypesForHighlighting(mimeTypeHint?: string) { highlightingLoaded = true; } -export function loadHighlightingTheme(themeName: string) { +export async function loadHighlightingTheme(themeName: string) { const themePrefix = "default:"; let theme: Theme | null = null; if (themeName.includes(themePrefix)) { @@ -108,7 +108,7 @@ export function loadHighlightingTheme(themeName: string) { theme = Themes.default; } - loadTheme(theme); + await loadTheme(theme); } /** From 7c08864444cc95a5c572501e62ff026a29f1737d Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 20 Oct 2025 11:06:26 +0300 Subject: [PATCH 27/36] feat(client/print): enforce VS code theme when printing code notes --- apps/client/src/services/syntax_highlight.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/client/src/services/syntax_highlight.ts b/apps/client/src/services/syntax_highlight.ts index e0ce55f42..2a545fc8b 100644 --- a/apps/client/src/services/syntax_highlight.ts +++ b/apps/client/src/services/syntax_highlight.ts @@ -101,14 +101,13 @@ export async function ensureMimeTypesForHighlighting(mimeTypeHint?: string) { export async function loadHighlightingTheme(themeName: string) { const themePrefix = "default:"; let theme: Theme | null = null; - if (themeName.includes(themePrefix)) { + if (glob.device === "print") { + theme = Themes.vs; + } else if (themeName.includes(themePrefix)) { theme = Themes[themeName.substring(themePrefix.length)]; } - if (!theme) { - theme = Themes.default; - } - await loadTheme(theme); + await loadTheme(theme ?? Themes.default); } /** From 1ae32c4547383fad1678aef3a45e706048fac750 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 20 Oct 2025 11:27:21 +0300 Subject: [PATCH 28/36] feat(client/print): integrate old print stylesheet --- apps/client/src/print.css | 147 +++++++++++- apps/client/src/stylesheets/print.css | 322 -------------------------- 2 files changed, 146 insertions(+), 323 deletions(-) delete mode 100644 apps/client/src/stylesheets/print.css diff --git a/apps/client/src/print.css b/apps/client/src/print.css index 5d31138ce..b935d9eb7 100644 --- a/apps/client/src/print.css +++ b/apps/client/src/print.css @@ -1,10 +1,155 @@ +:root { + --print-font-size: 11pt; + --ck-content-color-image-caption-background: transparent !important; +} + html, body { width: 100%; height: 100%; + color: black; +} + +@page { + margin: 2cm; } .note-list-widget.full-height, .note-list-widget.full-height .note-list-widget-content { height: unset !important; -} \ No newline at end of file +} + +.component { + contain: none !important; +} + +.ck-content { + font-size: var(--print-font-size); + text-align: justify; +} + +.ck-content figcaption { + font-style: italic; +} + +.ck-content a { + text-decoration: none; +} + +.ck-content a:not([href^="#root/"]) { + text-decoration: underline; + color: #374a75; +} + +.ck-content .todo-list__label * { + -webkit-print-color-adjust: exact; + print-color-adjust: exact; +} + +@supports selector(.todo-list__label__description:has(*)) and (height: 1lh) { + .ck-content .todo-list__label__description { + /* The percentage of the line height that the check box occupies */ + --box-ratio: 0.75; + /* The size of the gap between the check box and the caption */ + --box-text-gap: 0.25em; + + --box-size: calc(1lh * var(--box-ratio)); + --box-vert-offset: calc((1lh - var(--box-size)) / 2); + + display: inline-block; + padding-inline-start: calc(var(--box-size) + var(--box-text-gap)); + /* Source: https://pictogrammers.com/library/mdi/icon/checkbox-blank-outline/ */ + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3e%3cpath d='M19%2c3H5C3.89%2c3 3%2c3.89 3%2c5V19A2%2c2 0 0%2c0 5%2c21H19A2%2c2 0 0%2c0 21%2c19V5C21%2c3.89 20.1%2c3 19%2c3M19%2c5V19H5V5H19Z' /%3e%3c/svg%3e"); + background-position: 0 var(--box-vert-offset); + background-size: var(--box-size); + background-repeat: no-repeat; + } + + .ck-content .todo-list__label:has(input[type="checkbox"]:checked) .todo-list__label__description { + /* Source: https://pictogrammers.com/library/mdi/icon/checkbox-outline/ */ + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3e%3cpath d='M19%2c3H5A2%2c2 0 0%2c0 3%2c5V19A2%2c2 0 0%2c0 5%2c21H19A2%2c2 0 0%2c0 21%2c19V5A2%2c2 0 0%2c0 19%2c3M19%2c5V19H5V5H19M10%2c17L6%2c13L7.41%2c11.58L10%2c14.17L16.59%2c7.58L18%2c9' /%3e%3c/svg%3e"); + } + + .ck-content .todo-list__label input[type="checkbox"] { + display: none !important; + } +} + +/* #region Footnotes */ +.footnote-reference a, +.footnote-back-link a { + text-decoration: none !important; +} + +li.footnote-item { + position: relative; + width: fit-content; +} + +.ck-content .footnote-back-link { + margin-right: 0.25em; +} + +.ck-content .footnote-content { + display: inline-block; + width: unset; +} +/* #endregion */ + +/* #region Widows and orphans */ +p, +blockquote { + widows: 4; + orphans: 4; +} + +pre > code { + widows: 6; + orphans: 6; + overflow: auto; + white-space: pre-wrap !important; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + page-break-after: avoid; + break-after: avoid; +} +/* #endregion */ + +/* #region Tables */ +.table thead th, +.table td, +.table th { + /* Fix center vertical alignment of table cells */ + vertical-align: middle; +} + +pre { + box-shadow: unset !important; + border: 0.75pt solid gray !important; + border-radius: 2pt !important; +} + +th, +span[style] { + print-color-adjust: exact; + -webkit-print-color-adjust: exact; +} +/* #endregion */ + +/* #region Page breaks */ +.page-break { + page-break-after: always; + break-after: always; +} + +.page-break > *, +.page-break::after { + display: none !important; +} +/* #endregion */ \ No newline at end of file diff --git a/apps/client/src/stylesheets/print.css b/apps/client/src/stylesheets/print.css deleted file mode 100644 index 842570bde..000000000 --- a/apps/client/src/stylesheets/print.css +++ /dev/null @@ -1,322 +0,0 @@ -:root { - --main-background-color: white; - --root-background: var(--main-background-color); - --launcher-pane-background-color: var(--main-background-color); - --main-text-color: black; - --input-text-color: var(--main-text-color); - - --print-font-size: 11pt; -} - -@page { - margin: 2cm; -} - -.ck-content { - font-size: var(--print-font-size); - text-align: justify; -} - -.note-detail-readonly-text { - padding: 0 !important; -} - -.no-print, -.no-print *, -.tab-row-container, -.tab-row-widget, -.title-bar-buttons, -#launcher-pane, -#left-pane, -#center-pane > *:not(.split-note-container-widget), -#right-pane, -.title-row .note-icon-widget, -.title-row .icon-action, -.ribbon-container, -.promoted-attributes-widget, -.scroll-padding-widget, -.note-list-widget, -.spacer { - display: none !important; -} - -body.mobile #mobile-sidebar-wrapper, -body.mobile .classic-toolbar-widget, -body.mobile .action-button { - display: none !important; -} - -body.mobile #detail-container { - max-height: unset; -} - -body.mobile .note-title-widget { - padding: 0 !important; -} - -body, -#root-widget, -#rest-pane > div.component:first-child, -.note-detail-printable, -.note-detail-editable-text-editor { - height: unset !important; - overflow: auto; -} - -.ck.ck-editor__editable_inline { - overflow: hidden !important; -} - -.note-title-widget input, -.note-detail-editable-text, -.note-detail-editable-text-editor { - padding: 0 !important; -} - -html, -body { - width: unset !important; - height: unset !important; - overflow: visible; - position: unset; - /* https://github.com/zadam/trilium/issues/3202 */ - color: black; -} - -#root-widget, -#horizontal-main-container, -#rest-pane, -#vertical-main-container, -#center-pane, -.split-note-container-widget, -.note-split:not(.hidden-ext), -body.mobile #mobile-rest-container { - display: block !important; - overflow: auto; - border-radius: 0 !important; -} - -#center-pane, -#rest-pane, -.note-split, -body.mobile #detail-container { - width: unset !important; - max-width: unset !important; -} - -.component { - contain: none !important; -} - -/* Respect page breaks */ -.page-break { - page-break-after: always; - break-after: always; -} - -.page-break > * { - display: none !important; -} - -.relation-map-wrapper { - height: 100vh !important; -} - -.table thead th, -.table td, -.table th { - /* Fix center vertical alignment of table cells */ - vertical-align: middle; -} - -pre { - box-shadow: unset !important; - border: 0.75pt solid gray !important; - border-radius: 2pt !important; -} - -th, -span[style] { - print-color-adjust: exact; - -webkit-print-color-adjust: exact; -} - -/* - * Text note specific fixes - */ -.ck-widget { - outline: none !important; -} - -.ck-placeholder, -.ck-widget__type-around, -.ck-widget__selection-handle { - display: none !important; -} - -.ck-widget.table td.ck-editor__nested-editable.ck-editor__nested-editable_focused, -.ck-widget.table td.ck-editor__nested-editable:focus, -.ck-widget.table th.ck-editor__nested-editable.ck-editor__nested-editable_focused, -.ck-widget.table th.ck-editor__nested-editable:focus { - background: unset !important; - outline: unset !important; -} - -.include-note .include-note-content { - max-height: unset !important; - overflow: unset !important; -} - -/* TODO: This will break once we translate the language */ -.ck-content pre[data-language="Auto-detected"]:after { - display: none !important; -} - -/* - * Code note specific fixes. - */ -.note-detail-code pre { - border: unset !important; - border-radius: unset !important; -} - -/* - * Links - */ - -.note-detail-printable a { - text-decoration: none; -} - -.note-detail-printable a:not([href^="#root/"]) { - text-decoration: underline; - color: #374a75; -} - -.note-detail-printable a::after { - /* Hide the external link trailing arrow */ - display: none !important; -} - -/* - * TODO list check boxes - */ - -.note-detail-printable .todo-list__label * { - -webkit-print-color-adjust: exact; - print-color-adjust: exact; -} - -@supports selector(.todo-list__label__description:has(*)) and (height: 1lh) { - .note-detail-printable .todo-list__label__description { - /* The percentage of the line height that the check box occupies */ - --box-ratio: 0.75; - /* The size of the gap between the check box and the caption */ - --box-text-gap: 0.25em; - - --box-size: calc(1lh * var(--box-ratio)); - --box-vert-offset: calc((1lh - var(--box-size)) / 2); - - display: inline-block; - padding-inline-start: calc(var(--box-size) + var(--box-text-gap)); - /* Source: https://pictogrammers.com/library/mdi/icon/checkbox-blank-outline/ */ - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3e%3cpath d='M19%2c3H5C3.89%2c3 3%2c3.89 3%2c5V19A2%2c2 0 0%2c0 5%2c21H19A2%2c2 0 0%2c0 21%2c19V5C21%2c3.89 20.1%2c3 19%2c3M19%2c5V19H5V5H19Z' /%3e%3c/svg%3e"); - background-position: 0 var(--box-vert-offset); - background-size: var(--box-size); - background-repeat: no-repeat; - } - - .note-detail-printable .todo-list__label:has(input[type="checkbox"]:checked) .todo-list__label__description { - /* Source: https://pictogrammers.com/library/mdi/icon/checkbox-outline/ */ - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3e%3cpath d='M19%2c3H5A2%2c2 0 0%2c0 3%2c5V19A2%2c2 0 0%2c0 5%2c21H19A2%2c2 0 0%2c0 21%2c19V5A2%2c2 0 0%2c0 19%2c3M19%2c5V19H5V5H19M10%2c17L6%2c13L7.41%2c11.58L10%2c14.17L16.59%2c7.58L18%2c9' /%3e%3c/svg%3e"); - } - - .note-detail-printable .todo-list__label input[type="checkbox"] { - display: none !important; - } -} - -/* - * Blockquotes - */ - -.note-detail-printable blockquote { - box-shadow: unset; -} - -/* - * Figures - */ - -.note-detail-printable figcaption { - --accented-background-color: transparent; - - font-style: italic; -} - -/* - * Footnotes - */ - -.note-detail-printable .footnote-reference a, -.footnote-back-link a { - text-decoration: none; -} - -/* Make the "^" link cover the whole area of the footnote item */ - -.footnote-section { - clear: both; -} - -.note-detail-printable li.footnote-item { - position: relative; - width: fit-content; -} - -.note-detail-printable .footnote-back-link, -.note-detail-printable .footnote-back-link *, -.note-detail-printable .footnote-back-link a { - display: block; - position: absolute; - - top: 0; - inset-inline-start: 0; - width: 100%; - height: 100%; -} - -.note-detail-printable .footnote-back-link a { - color: transparent; -} - -.note-detail-printable .footnote-content { - display: inline-block; - width: unset; -} - -/* - * Widows and orphans - */ -p, -blockquote { - widows: 4; - orphans: 4; -} - -pre > code { - widows: 6; - orphans: 6; - overflow: auto; - white-space: pre-wrap !important; -} - -h1, -h2, -h3, -h4, -h5, -h6 { - page-break-after: avoid; - break-after: avoid; -} From e069d87fe81e6c118810420b00e79082382d0e5f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 20 Oct 2025 12:39:38 +0300 Subject: [PATCH 29/36] fix(client/print): wrong entrypoint in prod --- apps/client/vite.config.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/vite.config.mts b/apps/client/vite.config.mts index 3dd52b6b9..5a3053915 100644 --- a/apps/client/vite.config.mts +++ b/apps/client/vite.config.mts @@ -77,7 +77,7 @@ export default defineConfig(() => ({ 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.ts") + print: join(__dirname, "src", "print.tsx") }, output: { entryFileNames: "src/[name].js", From e6c8f238f9b6c5eec5dfbe1f5c11c02ee41eff78 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 20 Oct 2025 12:40:41 +0300 Subject: [PATCH 30/36] fix(client/print): stylesheet not loading in prod --- apps/client/src/print.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/print.tsx b/apps/client/src/print.tsx index 86d98b113..697ab1a6b 100644 --- a/apps/client/src/print.tsx +++ b/apps/client/src/print.tsx @@ -1,7 +1,6 @@ import FNote from "./entities/fnote"; import { render } from "preact"; import { CustomNoteList } from "./widgets/collections/NoteList"; -import "./print.css"; import { useCallback, useLayoutEffect, useRef } from "preact/hooks"; import content_renderer from "./services/content_renderer"; @@ -15,6 +14,7 @@ async function main() { const noteId = notePath.split("/").at(-1); if (!noteId) return; + await import("./print.css"); const froca = (await import("./services/froca")).default; const note = await froca.getNote(noteId); From 29d6784c59f6e1a54b5e9ad9700275dad8d4463e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 20 Oct 2025 12:46:24 +0300 Subject: [PATCH 31/36] feat(client/print): render 404 errors --- apps/client/src/print.tsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/apps/client/src/print.tsx b/apps/client/src/print.tsx index 697ab1a6b..4aff8729b 100644 --- a/apps/client/src/print.tsx +++ b/apps/client/src/print.tsx @@ -18,18 +18,19 @@ async function main() { const froca = (await import("./services/froca")).default; const note = await froca.getNote(noteId); - if (!note) return; - render(, document.body); + render(, document.body); } -function App({ note }: { note: FNote }) { +function App({ note, noteId }: { note: FNote | null | undefined, noteId: string }) { const sentReadyEvent = useRef(false); const onReady = useCallback(() => { if (sentReadyEvent.current) return; window.dispatchEvent(new Event("note-ready")); sentReadyEvent.current = true; }, []); - const props: RendererProps = { note, onReady }; + const props: RendererProps | undefined | null = note && { note, onReady }; + + if (!note || !props) return return ( <> @@ -74,4 +75,13 @@ function CollectionRenderer({ note, onReady }: RendererProps) { />; } +function Error404({ noteId }: { noteId: string }) { + return ( +
+

The note you are trying to print could not be found.

+ {noteId} +
+ ) +} + main(); From acae069b9eb01888721bee10d0821e03d6869431 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 20 Oct 2025 12:56:46 +0300 Subject: [PATCH 32/36] chore(client/print): fix typecheck issues --- _regroup/spec/support/etapi.ts | 3 --- apps/client/src/widgets/collections/NoteList.tsx | 5 +++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/_regroup/spec/support/etapi.ts b/_regroup/spec/support/etapi.ts index 307868d7d..b32ba38e7 100644 --- a/_regroup/spec/support/etapi.ts +++ b/_regroup/spec/support/etapi.ts @@ -1,4 +1,3 @@ -import type child_process from "child_process"; import { describe, beforeAll, afterAll } from "vitest"; let etapiAuthToken: string | undefined; @@ -12,8 +11,6 @@ type SpecDefinitionsFunc = () => void; function describeEtapi(description: string, specDefinitions: SpecDefinitionsFunc): void { describe(description, () => { - let appProcess: ReturnType; - beforeAll(async () => {}); afterAll(() => {}); diff --git a/apps/client/src/widgets/collections/NoteList.tsx b/apps/client/src/widgets/collections/NoteList.tsx index f9c9ba5d1..76deeeffe 100644 --- a/apps/client/src/widgets/collections/NoteList.tsx +++ b/apps/client/src/widgets/collections/NoteList.tsx @@ -23,7 +23,7 @@ interface NoteListProps { isEnabled: boolean; ntxId: string | null | undefined; media: ViewModeMedia; - onReady: () => void; + onReady?: () => void; } export default function NoteList(props: Pick) { @@ -36,7 +36,7 @@ export function SearchNoteList(props: Omit } -export function CustomNoteList({ note, isEnabled: shouldEnable, notePath, highlightedTokens, displayOnlyCollections, ntxId, ...restProps }: NoteListProps) { +export function CustomNoteList({ note, isEnabled: shouldEnable, notePath, highlightedTokens, displayOnlyCollections, ntxId, onReady, ...restProps }: NoteListProps) { const widgetRef = useRef(null); const viewType = useNoteViewType(note); const noteIds = useNoteIds(note, viewType, ntxId); @@ -79,6 +79,7 @@ export function CustomNoteList({ note, isEnabled: shouldEnable highlightedTokens, viewConfig: viewModeConfig[0], saveConfig: viewModeConfig[1], + onReady: onReady ?? (() => {}), ...restProps } } From 04f67776279dc3a80680da9549a153a87945d1d5 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 20 Oct 2025 14:04:04 +0300 Subject: [PATCH 33/36] chore(client/print): address requested changes --- apps/client/src/print.tsx | 1 + apps/client/src/types.d.ts | 3 +++ apps/client/src/widgets/note_detail.ts | 7 ++++++- apps/server/src/services/window.ts | 17 ++++++++++------- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/apps/client/src/print.tsx b/apps/client/src/print.tsx index 4aff8729b..e762f5781 100644 --- a/apps/client/src/print.tsx +++ b/apps/client/src/print.tsx @@ -26,6 +26,7 @@ function App({ note, noteId }: { note: FNote | null | undefined, noteId: string const onReady = useCallback(() => { if (sentReadyEvent.current) return; window.dispatchEvent(new Event("note-ready")); + window._noteReady = true; sentReadyEvent.current = true; }, []); const props: RendererProps | undefined | null = note && { note, onReady }; diff --git a/apps/client/src/types.d.ts b/apps/client/src/types.d.ts index 2546d2ffa..c5a93bd0a 100644 --- a/apps/client/src/types.d.ts +++ b/apps/client/src/types.d.ts @@ -59,6 +59,9 @@ declare global { process?: ElectronProcess; glob?: CustomGlobals; + /** On the printing endpoint, set to true when the note has fully loaded and is ready to be printed/exported as PDF. */ + _noteReady?: boolean; + EXCALIDRAW_ASSET_PATH?: string; } diff --git a/apps/client/src/widgets/note_detail.ts b/apps/client/src/widgets/note_detail.ts index a701d442b..a976b97ce 100644 --- a/apps/client/src/widgets/note_detail.ts +++ b/apps/client/src/widgets/note_detail.ts @@ -322,7 +322,12 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { iframe.className = "print-iframe"; document.body.appendChild(iframe); iframe.onload = () => { - if (!iframe.contentWindow) return; + if (!iframe.contentWindow) { + toast.closePersistent("printing"); + document.body.removeChild(iframe); + return; + } + iframe.contentWindow.addEventListener("note-ready", () => { toast.closePersistent("printing"); iframe.contentWindow?.print(); diff --git a/apps/server/src/services/window.ts b/apps/server/src/services/window.ts index 69d273158..459ebdf59 100644 --- a/apps/server/src/services/window.ts +++ b/apps/server/src/services/window.ts @@ -84,19 +84,18 @@ interface ExportAsPdfOpts { electron.ipcMain.on("print-note", async (e, { notePath }: PrintOpts) => { const browserWindow = await getBrowserWindowForPrinting(e, notePath); browserWindow.webContents.print({}, (success, failureReason) => { - if (success) { - browserWindow.destroy(); - } else { + if (!success) { electron.dialog.showErrorBox(t("pdf.unable-to-print"), failureReason); } e.sender.send("print-done"); + browserWindow.destroy(); }); }); electron.ipcMain.on("export-as-pdf", async (e, { title, notePath, landscape, pageSize }: ExportAsPdfOpts) => { - async function print() { - const browserWindow = await getBrowserWindowForPrinting(e, notePath); + const browserWindow = await getBrowserWindowForPrinting(e, notePath); + async function print() { const filePath = electron.dialog.showSaveDialogSync(browserWindow, { defaultPath: formatDownloadTitle(title, "file", "application/pdf"), filters: [ @@ -138,8 +137,12 @@ electron.ipcMain.on("export-as-pdf", async (e, { title, notePath, landscape, pag electron.shell.openPath(filePath); } - await print(); - e.sender.send("print-done"); + try { + await print(); + } finally { + e.sender.send("print-done"); + browserWindow.destroy(); + } }); async function getBrowserWindowForPrinting(e: IpcMainEvent, notePath: string) { From cc09a450c9dbb3b6e1f1437f457d0c96ecc8194b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 20 Oct 2025 14:16:20 +0300 Subject: [PATCH 34/36] docs(user): improve & update documentation for printing --- .../doc_notes/en/User Guide/!!!meta.json | 2 +- .../Advanced Usage/Attributes/Labels.html | 4 +- ...e.png => 1_Printing & Exporting as PD.png} | Bin .../Notes/Export as PDF.html | 42 ------- ...age.png => Printing & Exporting as PD.png} | Bin .../Notes/Printing & Exporting as PDF.html | 111 +++++++++++++++++ .../Collections/Presentation View.html | 114 +++++++++--------- .../Custom app-wide CSS.html | 76 ++++++------ docs/User Guide/!!!meta.json | 85 ++++++++++--- ...e.png => 1_Printing & Exporting as PD.png} | Bin .../Notes/Export as PDF.md | 38 ------ ...age.png => Printing & Exporting as PD.png} | Bin .../Notes/Printing & Exporting as PDF.md | 75 ++++++++++++ .../User Guide/Feature Highlights.md | 2 +- .../Note Types/Text/Insert buttons.md | 2 +- 15 files changed, 351 insertions(+), 200 deletions(-) rename apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes/{1_Export as PDF_image.png => 1_Printing & Exporting as PD.png} (100%) delete mode 100644 apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes/Export as PDF.html rename apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes/{Export as PDF_image.png => Printing & Exporting as PD.png} (100%) create mode 100644 apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes/Printing & Exporting as PDF.html rename docs/User Guide/User Guide/Basic Concepts and Features/Notes/{1_Export as PDF_image.png => 1_Printing & Exporting as PD.png} (100%) delete mode 100644 docs/User Guide/User Guide/Basic Concepts and Features/Notes/Export as PDF.md rename docs/User Guide/User Guide/Basic Concepts and Features/Notes/{Export as PDF_image.png => Printing & Exporting as PD.png} (100%) create mode 100644 docs/User Guide/User Guide/Basic Concepts and Features/Notes/Printing & Exporting as PDF.md diff --git a/apps/server/src/assets/doc_notes/en/User Guide/!!!meta.json b/apps/server/src/assets/doc_notes/en/User Guide/!!!meta.json index 12fa68969..14e062249 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/!!!meta.json +++ b/apps/server/src/assets/doc_notes/en/User Guide/!!!meta.json @@ -1 +1 @@ -[{"id":"_help_BOCnjTMBCoxW","title":"Feature Highlights","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Feature Highlights"},{"name":"iconClass","value":"bx bx-star","type":"label"}]},{"id":"_help_Otzi9La2YAUX","title":"Installation & Setup","type":"book","attributes":[{"name":"iconClass","value":"bx bx-cog","type":"label"}],"children":[{"id":"_help_poXkQfguuA0U","title":"Desktop Installation","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Desktop Installation"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_nRqcgfTb97uV","title":"Using the desktop application as a server","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Desktop Installation/Using the desktop application "},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_WOcw2SLH6tbX","title":"Server Installation","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_Dgg7bR3b6K9j","title":"1. Installing the server","type":"book","attributes":[{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_3tW6mORuTHnB","title":"Packaged version for Linux","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Packaged version for Linux"},{"name":"iconClass","value":"bx bxl-tux","type":"label"}]},{"id":"_help_rWX5eY045zbE","title":"Using Docker","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Using Docker"},{"name":"iconClass","value":"bx bxl-docker","type":"label"}]},{"id":"_help_moVgBcoxE3EK","title":"On NixOS","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/On NixOS"},{"name":"iconClass","value":"bx bxl-tux","type":"label"}]},{"id":"_help_J1Bb6lVlwU5T","title":"Manually","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Manually"},{"name":"iconClass","value":"bx bx-code-alt","type":"label"}]},{"id":"_help_DCmT6e7clMoP","title":"Using Kubernetes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Using Kubernetes"},{"name":"iconClass","value":"bx bxl-kubernetes","type":"label"}]},{"id":"_help_klCWNks3ReaQ","title":"Multiple server instances","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Multiple server instances"},{"name":"iconClass","value":"bx bxs-user-account","type":"label"}]}]},{"id":"_help_vcjrb3VVYPZI","title":"2. Reverse proxy","type":"book","attributes":[{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_ud6MShXL4WpO","title":"Nginx","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/2. Reverse proxy/Nginx"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_fDLvzOx29Pfg","title":"Apache using Docker","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/2. Reverse proxy/Apache using Docker"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_LLzSMXACKhUs","title":"Trusted proxy","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/2. Reverse proxy/Trusted proxy"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_l2VkvOwUNfZj","title":"HTTPS (TLS)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/HTTPS (TLS)"},{"name":"iconClass","value":"bx bx-lock-alt","type":"label"}]},{"id":"_help_0hzsNCP31IAB","title":"Authentication","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/Authentication"},{"name":"iconClass","value":"bx bx-user","type":"label"}]},{"id":"_help_7DAiwaf8Z7Rz","title":"Multi-Factor Authentication","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/Multi-Factor Authentication"},{"name":"iconClass","value":"bx bx-stopwatch","type":"label"}]}]},{"id":"_help_cbkrhQjrkKrh","title":"Synchronization","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Synchronization"},{"name":"iconClass","value":"bx bx-sync","type":"label"}]},{"id":"_help_RDslemsQ6gCp","title":"Mobile Frontend","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Mobile Frontend"},{"name":"iconClass","value":"bx bx-mobile-alt","type":"label"}]},{"id":"_help_MtPxeAWVAzMg","title":"Web Clipper","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Web Clipper"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_n1lujUxCwipy","title":"Upgrading TriliumNext","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Upgrading TriliumNext"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_ODY7qQn5m2FT","title":"Backup","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Backup"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_tAassRL4RSQL","title":"Data directory","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Data directory"},{"name":"iconClass","value":"bx bx-folder-open","type":"label"}]}]},{"id":"_help_gh7bpGYxajRS","title":"Basic Concepts and Features","type":"book","attributes":[{"name":"iconClass","value":"bx bx-help-circle","type":"label"}],"children":[{"id":"_help_Vc8PjrjAGuOp","title":"UI Elements","type":"book","attributes":[{"name":"iconClass","value":"bx bx-window-alt","type":"label"}],"children":[{"id":"_help_x0JgW8UqGXvq","title":"Vertical and horizontal layout","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Vertical and horizontal layout"},{"name":"iconClass","value":"bx bxs-layout","type":"label"}]},{"id":"_help_x3i7MxGccDuM","title":"Global menu","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Global menu"},{"name":"iconClass","value":"bx bx-menu","type":"label"}]},{"id":"_help_oPVyFC7WL2Lp","title":"Note Tree","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree"},{"name":"iconClass","value":"bx bxs-tree-alt","type":"label"}],"children":[{"id":"_help_YtSN43OrfzaA","title":"Note tree contextual menu","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Note tree contextual menu"},{"name":"iconClass","value":"bx bx-menu","type":"label"}]},{"id":"_help_yTjUdsOi4CIE","title":"Multiple selection","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Multiple selection"},{"name":"iconClass","value":"bx bx-list-plus","type":"label"}]},{"id":"_help_DvdZhoQZY9Yd","title":"Keyboard shortcuts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Keyboard shortcuts"},{"name":"iconClass","value":"bx bxs-keyboard","type":"label"}]}]},{"id":"_help_BlN9DFI679QC","title":"Ribbon","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Ribbon"},{"name":"iconClass","value":"bx bx-dots-horizontal","type":"label"}]},{"id":"_help_3seOhtN8uLIY","title":"Tabs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Tabs"},{"name":"iconClass","value":"bx bx-dock-top","type":"label"}]},{"id":"_help_xYmIYSP6wE3F","title":"Launch Bar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Launch Bar"},{"name":"iconClass","value":"bx bx-sidebar","type":"label"}]},{"id":"_help_8YBEPzcpUgxw","title":"Note buttons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note buttons"},{"name":"iconClass","value":"bx bx-dots-vertical-rounded","type":"label"}]},{"id":"_help_4TIF1oA4VQRO","title":"Options","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Options"},{"name":"iconClass","value":"bx bx-cog","type":"label"}]},{"id":"_help_luNhaphA37EO","title":"Split View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Split View"},{"name":"iconClass","value":"bx bx-dock-right","type":"label"}]},{"id":"_help_XpOYSgsLkTJy","title":"Floating buttons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Floating buttons"},{"name":"iconClass","value":"bx bx-rectangle","type":"label"}]},{"id":"_help_RnaPdbciOfeq","title":"Right Sidebar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Right Sidebar"},{"name":"iconClass","value":"bx bxs-dock-right","type":"label"}]},{"id":"_help_r5JGHN99bVKn","title":"Recent Changes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Recent Changes"},{"name":"iconClass","value":"bx bx-history","type":"label"}]},{"id":"_help_ny318J39E5Z0","title":"Zoom","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Zoom"},{"name":"iconClass","value":"bx bx-zoom-in","type":"label"}]},{"id":"_help_ZjLYv08Rp3qC","title":"Quick edit","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Quick edit"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_lgKX7r3aL30x","title":"Note Tooltip","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tooltip"},{"name":"iconClass","value":"bx bx-message-detail","type":"label"}]}]},{"id":"_help_BFs8mudNFgCS","title":"Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes"},{"name":"iconClass","value":"bx bx-notepad","type":"label"}],"children":[{"id":"_help_p9kXRFAkwN4o","title":"Note Icons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Note Icons"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]},{"id":"_help_0vhv7lsOLy82","title":"Attachments","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Attachments"},{"name":"iconClass","value":"bx bx-paperclip","type":"label"}]},{"id":"_help_IakOLONlIfGI","title":"Cloning Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Cloning Notes"},{"name":"iconClass","value":"bx bx-duplicate","type":"label"}],"children":[{"id":"_help_TBwsyfadTA18","title":"Branch prefix","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Cloning Notes/Branch prefix"},{"name":"iconClass","value":"bx bx-rename","type":"label"}]}]},{"id":"_help_bwg0e8ewQMak","title":"Protected Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Protected Notes"},{"name":"iconClass","value":"bx bx-lock-alt","type":"label"}]},{"id":"_help_MKmLg5x6xkor","title":"Archived Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Archived Notes"},{"name":"iconClass","value":"bx bx-box","type":"label"}]},{"id":"_help_vZWERwf8U3nx","title":"Note Revisions","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Note Revisions"},{"name":"iconClass","value":"bx bx-history","type":"label"}]},{"id":"_help_aGlEvb9hyDhS","title":"Sorting Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Sorting Notes"},{"name":"iconClass","value":"bx bx-sort-up","type":"label"}]},{"id":"_help_NRnIZmSMc5sj","title":"Export as PDF","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Export as PDF"},{"name":"iconClass","value":"bx bxs-file-pdf","type":"label"}]},{"id":"_help_CoFPLs3dRlXc","title":"Read-Only Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Read-Only Notes"},{"name":"iconClass","value":"bx bx-edit-alt","type":"label"}]},{"id":"_help_0ESUbbAxVnoK","title":"Note List","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Note List"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]}]},{"id":"_help_wArbEsdSae6g","title":"Navigation","type":"book","attributes":[{"name":"iconClass","value":"bx bx-navigation","type":"label"}],"children":[{"id":"_help_kBrnXNG3Hplm","title":"Tree Concepts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Tree Concepts"},{"name":"iconClass","value":"bx bx-pyramid","type":"label"}]},{"id":"_help_MMiBEQljMQh2","title":"Note Navigation","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Note Navigation"},{"name":"iconClass","value":"bx bxs-navigation","type":"label"}]},{"id":"_help_Ms1nauBra7gq","title":"Quick search","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Quick search"},{"name":"iconClass","value":"bx bx-search-alt-2","type":"label"}]},{"id":"_help_F1r9QtzQLZqm","title":"Jump to...","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Jump to"},{"name":"iconClass","value":"bx bx-send","type":"label"}]},{"id":"_help_eIg8jdvaoNNd","title":"Search","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Search"},{"name":"iconClass","value":"bx bx-search-alt-2","type":"label"}]},{"id":"_help_u3YFHC9tQlpm","title":"Bookmarks","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Bookmarks"},{"name":"iconClass","value":"bx bx-bookmarks","type":"label"}]},{"id":"_help_OR8WJ7Iz9K4U","title":"Note Hoisting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Note Hoisting"},{"name":"iconClass","value":"bx bxs-chevrons-up","type":"label"}]},{"id":"_help_ZjLYv08Rp3qC","title":"Quick edit","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Quick edit.clone"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_9sRHySam5fXb","title":"Workspaces","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Workspaces"},{"name":"iconClass","value":"bx bx-door-open","type":"label"}]},{"id":"_help_xWtq5NUHOwql","title":"Similar Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Similar Notes"},{"name":"iconClass","value":"bx bx-bar-chart","type":"label"}]},{"id":"_help_McngOG2jbUWX","title":"Search in note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Search in note"},{"name":"iconClass","value":"bx bx-search-alt-2","type":"label"}]}]},{"id":"_help_A9Oc6YKKc65v","title":"Keyboard Shortcuts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Keyboard Shortcuts"},{"name":"iconClass","value":"bx bxs-keyboard","type":"label"}]},{"id":"_help_Wy267RK4M69c","title":"Themes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Themes"},{"name":"iconClass","value":"bx bx-palette","type":"label"}],"children":[{"id":"_help_VbjZvtUek0Ln","title":"Theme Gallery","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Themes/Theme Gallery"},{"name":"iconClass","value":"bx bx-book-reader","type":"label"}]}]},{"id":"_help_mHbBMPDPkVV5","title":"Import & Export","type":"book","attributes":[{"name":"iconClass","value":"bx bx-import","type":"label"}],"children":[{"id":"_help_Oau6X9rCuegd","title":"Markdown","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/Markdown"},{"name":"iconClass","value":"bx bxl-markdown","type":"label"}],"children":[{"id":"_help_rJ9grSgoExl9","title":"Supported syntax","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/Markdown/Supported syntax"},{"name":"iconClass","value":"bx bx-code-alt","type":"label"}]}]},{"id":"_help_syuSEKf2rUGr","title":"Evernote","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/Evernote"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_GnhlmrATVqcH","title":"OneNote","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/OneNote"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_rC3pL2aptaRE","title":"Zen mode","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Zen mode"},{"name":"iconClass","value":"bx bxs-yin-yang","type":"label"}]}]},{"id":"_help_s3YCWHBfmYuM","title":"Quick Start","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Quick Start"},{"name":"iconClass","value":"bx bx-run","type":"label"}]},{"id":"_help_i6dbnitykE5D","title":"FAQ","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/FAQ"},{"name":"iconClass","value":"bx bx-question-mark","type":"label"}]},{"id":"_help_KSZ04uQ2D1St","title":"Note Types","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types"},{"name":"iconClass","value":"bx bx-edit","type":"label"}],"children":[{"id":"_help_iPIMuisry3hd","title":"Text","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text"},{"name":"iconClass","value":"bx bx-note","type":"label"}],"children":[{"id":"_help_NwBbFdNZ9h7O","title":"Block quotes & admonitions","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Block quotes & admonitions"},{"name":"iconClass","value":"bx bx-info-circle","type":"label"}]},{"id":"_help_oSuaNgyyKnhu","title":"Bookmarks","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Bookmarks"},{"name":"iconClass","value":"bx bx-bookmark","type":"label"}]},{"id":"_help_veGu4faJErEM","title":"Content language & Right-to-left support","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Content language & Right-to-le"},{"name":"iconClass","value":"bx bx-align-right","type":"label"}]},{"id":"_help_2x0ZAX9ePtzV","title":"Cut to subnote","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Cut to subnote"},{"name":"iconClass","value":"bx bx-cut","type":"label"}]},{"id":"_help_UYuUB1ZekNQU","title":"Developer-specific formatting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Developer-specific formatting"},{"name":"iconClass","value":"bx bx-code-alt","type":"label"}],"children":[{"id":"_help_QxEyIjRBizuC","title":"Code blocks","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Developer-specific formatting/Code blocks"},{"name":"iconClass","value":"bx bx-code","type":"label"}]}]},{"id":"_help_AgjCISero73a","title":"Footnotes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Footnotes"},{"name":"iconClass","value":"bx bx-bracket","type":"label"}]},{"id":"_help_nRhnJkTT8cPs","title":"Formatting toolbar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Formatting toolbar"},{"name":"iconClass","value":"bx bx-text","type":"label"}]},{"id":"_help_Gr6xFaF6ioJ5","title":"General formatting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/General formatting"},{"name":"iconClass","value":"bx bx-bold","type":"label"}]},{"id":"_help_AxshuNRegLAv","title":"Highlights list","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Highlights list"},{"name":"iconClass","value":"bx bx-highlight","type":"label"}]},{"id":"_help_mT0HEkOsz6i1","title":"Images","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Images"},{"name":"iconClass","value":"bx bx-image-alt","type":"label"}],"children":[{"id":"_help_0Ofbk1aSuVRu","title":"Image references","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Images/Image references"},{"name":"iconClass","value":"bx bxs-file-image","type":"label"}]}]},{"id":"_help_nBAXQFj20hS1","title":"Include Note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Include Note"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_CohkqWQC1iBv","title":"Insert buttons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Insert buttons"},{"name":"iconClass","value":"bx bx-plus","type":"label"}]},{"id":"_help_oiVPnW8QfnvS","title":"Keyboard shortcuts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Keyboard shortcuts"},{"name":"iconClass","value":"bx bxs-keyboard","type":"label"}]},{"id":"_help_QEAPj01N5f7w","title":"Links","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Links"},{"name":"iconClass","value":"bx bx-link-alt","type":"label"}],"children":[{"id":"_help_3IDVtesTQ8ds","title":"External links","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Links/External links"},{"name":"iconClass","value":"bx bx-link-external","type":"label"}]},{"id":"_help_hrZ1D00cLbal","title":"Internal (reference) links","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Links/Internal (reference) links"},{"name":"iconClass","value":"bx bx-link","type":"label"}]}]},{"id":"_help_S6Xx8QIWTV66","title":"Lists","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Lists"},{"name":"iconClass","value":"bx bx-list-ul","type":"label"}]},{"id":"_help_QrtTYPmdd1qq","title":"Markdown-like formatting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Markdown-like formatting"},{"name":"iconClass","value":"bx bxl-markdown","type":"label"}]},{"id":"_help_YfYAtQBcfo5V","title":"Math Equations","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Math Equations"},{"name":"iconClass","value":"bx bx-math","type":"label"}]},{"id":"_help_dEHYtoWWi8ct","title":"Other features","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Other features"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]},{"id":"_help_gLt3vA97tMcp","title":"Premium features","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Premium features"},{"name":"iconClass","value":"bx bx-star","type":"label"}],"children":[{"id":"_help_ZlN4nump6EbW","title":"Slash Commands","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Premium features/Slash Commands"},{"name":"iconClass","value":"bx bx-menu","type":"label"}]},{"id":"_help_pwc194wlRzcH","title":"Text Snippets","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Premium features/Text Snippets"},{"name":"iconClass","value":"bx bx-align-left","type":"label"}]}]},{"id":"_help_BFvAtE74rbP6","title":"Table of contents","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Table of contents"},{"name":"iconClass","value":"bx bx-heading","type":"label"}]},{"id":"_help_NdowYOC1GFKS","title":"Tables","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Tables"},{"name":"iconClass","value":"bx bx-table","type":"label"}]}]},{"id":"_help_6f9hih2hXXZk","title":"Code","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Code"},{"name":"iconClass","value":"bx bx-code","type":"label"}]},{"id":"_help_m523cpzocqaD","title":"Saved Search","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Saved Search"},{"name":"iconClass","value":"bx bx-file-find","type":"label"}]},{"id":"_help_iRwzGnHPzonm","title":"Relation Map","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Relation Map"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]},{"id":"_help_bdUJEHsAPYQR","title":"Note Map","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Note Map"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]},{"id":"_help_HcABDtFCkbFN","title":"Render Note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Render Note"},{"name":"iconClass","value":"bx bx-extension","type":"label"}]},{"id":"_help_GTwFsgaA0lCt","title":"Collections","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Collections"},{"name":"iconClass","value":"bx bx-book","type":"label"}],"children":[{"id":"_help_xWbu3jpNWapp","title":"Calendar View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Collections/Calendar View"},{"name":"iconClass","value":"bx bx-calendar","type":"label"}]},{"id":"_help_81SGnPGMk7Xc","title":"Geo Map View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Collections/Geo Map View"},{"name":"iconClass","value":"bx bx-map-alt","type":"label"}]},{"id":"_help_8QqnMzx393bx","title":"Grid View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Collections/Grid View"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]},{"id":"_help_mULW0Q3VojwY","title":"List View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Collections/List View"},{"name":"iconClass","value":"bx bx-list-ul","type":"label"}]},{"id":"_help_2FvYrpmOXm29","title":"Table View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Collections/Table View"},{"name":"iconClass","value":"bx bx-table","type":"label"}]},{"id":"_help_CtBQqbwXDx1w","title":"Board View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Collections/Board View"},{"name":"iconClass","value":"bx bx-columns","type":"label"}]},{"id":"_help_zP3PMqaG71Ct","title":"Presentation View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Collections/Presentation View"},{"name":"iconClass","value":"bx bx-slideshow","type":"label"}]}]},{"id":"_help_s1aBHPd79XYj","title":"Mermaid Diagrams","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Mermaid Diagrams"},{"name":"iconClass","value":"bx bx-selection","type":"label"}],"children":[{"id":"_help_RH6yLjjWJHof","title":"ELK layout","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Mermaid Diagrams/ELK layout"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]}]},{"id":"_help_grjYqerjn243","title":"Canvas","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Canvas"},{"name":"iconClass","value":"bx bx-pen","type":"label"}]},{"id":"_help_1vHRoWCEjj0L","title":"Web View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Web View"},{"name":"iconClass","value":"bx bx-globe-alt","type":"label"}]},{"id":"_help_gBbsAeiuUxI5","title":"Mind Map","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Mind Map"},{"name":"iconClass","value":"bx bx-sitemap","type":"label"}]},{"id":"_help_W8vYD3Q1zjCR","title":"File","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/File"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_BgmBlOIl72jZ","title":"Troubleshooting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting"},{"name":"iconClass","value":"bx bx-bug","type":"label"}],"children":[{"id":"_help_wy8So3yZZlH9","title":"Reporting issues","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Reporting issues"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_x59R8J8KV5Bp","title":"Anonymized Database","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Anonymized Database"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_qzNzp9LYQyPT","title":"Error logs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Error logs"},{"name":"iconClass","value":"bx bx-comment-error","type":"label"}],"children":[{"id":"_help_bnyigUA2UK7s","title":"Backend (server) logs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Error logs/Backend (server) logs"},{"name":"iconClass","value":"bx bx-server","type":"label"}]},{"id":"_help_9yEHzMyFirZR","title":"Frontend logs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Error logs/Frontend logs"},{"name":"iconClass","value":"bx bx-window-alt","type":"label"}]}]},{"id":"_help_vdlYGAcpXAgc","title":"Synchronization fails with 504 Gateway Timeout","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Synchronization fails with 504"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_s8alTXmpFR61","title":"Refreshing the application","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Refreshing the application"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_pKK96zzmvBGf","title":"Theme development","type":"book","attributes":[{"name":"iconClass","value":"bx bx-palette","type":"label"}],"children":[{"id":"_help_7NfNr5pZpVKV","title":"Creating a custom theme","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Creating a custom theme"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_WFGzWeUK6arS","title":"Customize the Next theme","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Customize the Next theme"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_WN5z4M8ASACJ","title":"Reference","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Reference"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_AlhDUqhENtH7","title":"Custom app-wide CSS","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Custom app-wide CSS"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_tC7s2alapj8V","title":"Advanced Usage","type":"book","attributes":[{"name":"iconClass","value":"bx bx-rocket","type":"label"}],"children":[{"id":"_help_zEY4DaJG4YT5","title":"Attributes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes"},{"name":"iconClass","value":"bx bx-list-check","type":"label"}],"children":[{"id":"_help_HI6GBBIduIgv","title":"Labels","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Labels"},{"name":"iconClass","value":"bx bx-hash","type":"label"}]},{"id":"_help_Cq5X6iKQop6R","title":"Relations","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Relations"},{"name":"iconClass","value":"bx bx-transfer","type":"label"}]},{"id":"_help_bwZpz2ajCEwO","title":"Attribute Inheritance","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Attribute Inheritance"},{"name":"iconClass","value":"bx bx-list-plus","type":"label"}]},{"id":"_help_OFXdgB2nNk1F","title":"Promoted Attributes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Promoted Attributes"},{"name":"iconClass","value":"bx bx-table","type":"label"}]}]},{"id":"_help_KC1HB96bqqHX","title":"Templates","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Templates"},{"name":"iconClass","value":"bx bx-copy","type":"label"}]},{"id":"_help_BCkXAVs63Ttv","title":"Note Map (Link map, Tree map)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Note Map (Link map, Tree map)"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]},{"id":"_help_R9pX4DGra2Vt","title":"Sharing","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Sharing"},{"name":"iconClass","value":"bx bx-share-alt","type":"label"}],"children":[{"id":"_help_Qjt68inQ2bRj","title":"Serving directly the content of a note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Sharing/Serving directly the content o"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_5668rwcirq1t","title":"Advanced Showcases","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_l0tKav7yLHGF","title":"Day Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases/Day Notes"},{"name":"iconClass","value":"bx bx-calendar","type":"label"}]},{"id":"_help_R7abl2fc6Mxi","title":"Weight Tracker","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases/Weight Tracker"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_xYjQUYhpbUEW","title":"Task Manager","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases/Task Manager"},{"name":"iconClass","value":"bx bx-calendar-check","type":"label"}]}]},{"id":"_help_J5Ex1ZrMbyJ6","title":"Custom Request Handler","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Custom Request Handler"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_d3fAXQ2diepH","title":"Custom Resource Providers","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Custom Resource Providers"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_pgxEVkzLl1OP","title":"ETAPI (REST API)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/ETAPI (REST API)"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_9qPsTWBorUhQ","title":"API Reference","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"/etapi/docs"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_47ZrP6FNuoG8","title":"Default Note Title","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Default Note Title"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_wX4HbRucYSDD","title":"Database","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database"},{"name":"iconClass","value":"bx bx-data","type":"label"}],"children":[{"id":"_help_oyIAJ9PvvwHX","title":"Manually altering the database","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database/Manually altering the database"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_YKWqdJhzi2VY","title":"SQL Console","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database/Manually altering the database/SQL Console"},{"name":"iconClass","value":"bx bx-data","type":"label"}]}]},{"id":"_help_6tZeKvSHEUiB","title":"Demo Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database/Demo Notes"},{"name":"iconClass","value":"bx bx-package","type":"label"}]}]},{"id":"_help_Gzjqa934BdH4","title":"Configuration (config.ini or environment variables)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Configuration (config.ini or e"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_c5xB8m4g2IY6","title":"Trilium instance","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Configuration (config.ini or environment variables)/Trilium instance"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_LWtBjFej3wX3","title":"Cross-Origin Resource Sharing (CORS)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Configuration (config.ini or environment variables)/Cross-Origin Resource Sharing "},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_ivYnonVFBxbQ","title":"Bulk Actions","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Bulk Actions"},{"name":"iconClass","value":"bx bx-list-plus","type":"label"}]},{"id":"_help_4FahAwuGTAwC","title":"Note source","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Note source"},{"name":"iconClass","value":"bx bx-code","type":"label"}]},{"id":"_help_1YeN2MzFUluU","title":"Technologies used","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used"},{"name":"iconClass","value":"bx bxs-component","type":"label"}],"children":[{"id":"_help_MI26XDLSAlCD","title":"CKEditor","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/CKEditor"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_N4IDkixaDG9C","title":"MindElixir","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/MindElixir"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_H0mM1lTxF9JI","title":"Excalidraw","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/Excalidraw"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_MQHyy2dIFgxS","title":"Leaflet","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/Leaflet"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_m1lbrzyKDaRB","title":"Note ID","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Note ID"},{"name":"iconClass","value":"bx bx-hash","type":"label"}]},{"id":"_help_0vTSyvhPTAOz","title":"Internal API","type":"book","attributes":[{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_z8O2VG4ZZJD7","title":"API Reference","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"/api/docs"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_2mUhVmZK8RF3","title":"Hidden Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Hidden Notes"},{"name":"iconClass","value":"bx bx-hide","type":"label"}]},{"id":"_help_uYF7pmepw27K","title":"Metrics","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Metrics"},{"name":"iconClass","value":"bx bxs-data","type":"label"}],"children":[{"id":"_help_bOP3TB56fL1V","title":"grafana-dashboard.json","type":"doc","attributes":[{"name":"iconClass","value":"bx bx-file","type":"label"}]}]}]},{"id":"_help_LMAv4Uy3Wk6J","title":"AI","type":"book","attributes":[{"name":"iconClass","value":"bx bx-bot","type":"label"}],"children":[{"id":"_help_GBBMSlVSOIGP","title":"Introduction","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI/Introduction"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_WkM7gsEUyCXs","title":"AI Provider Information","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI/AI Provider Information"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_7EdTxPADv95W","title":"Ollama","type":"book","attributes":[{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_vvUCN7FDkq7G","title":"Installing Ollama","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI/AI Provider Information/Ollama/Installing Ollama"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_ZavFigBX9AwP","title":"OpenAI","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI/AI Provider Information/OpenAI"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_e0lkirXEiSNc","title":"Anthropic","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI/AI Provider Information/Anthropic"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]}]},{"id":"_help_CdNpE2pqjmI6","title":"Scripting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting"},{"name":"iconClass","value":"bx bxs-file-js","type":"label"}],"children":[{"id":"_help_yIhgI5H7A2Sm","title":"Frontend Basics","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_es8OU2GuguFU","title":"Examples","type":"book","attributes":[{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_TjLYAo3JMO8X","title":"\"New Task\" launcher button","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Examples/New Task launcher button"},{"name":"iconClass","value":"bx bx-task","type":"label"}]},{"id":"_help_7kZPMD0uFwkH","title":"Downloading responses from Google Forms","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Examples/Downloading responses from Goo"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_DL92EjAaXT26","title":"Using promoted attributes to configure scripts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Examples/Using promoted attributes to c"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_GPERMystNGTB","title":"Events","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Events"},{"name":"iconClass","value":"bx bx-rss","type":"label"}]},{"id":"_help_MgibgPcfeuGz","title":"Custom Widgets","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Custom Widgets"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_YNxAqkI5Kg1M","title":"Word count widget","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Custom Widgets/Word count widget"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_SynTBQiBsdYJ","title":"Widget Basics","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Custom Widgets/Widget Basics"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_GLks18SNjxmC","title":"Script API","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Script API"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_Q2z6av6JZVWm","title":"Frontend API","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://triliumnext.github.io/Notes/Script%20API/interfaces/Frontend_Script_API.Api.html"},{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_habiZ3HU8Kw8","title":"FNote","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://triliumnext.github.io/Notes/Script%20API/classes/Frontend_Script_API.FNote.html"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_MEtfsqa5VwNi","title":"Backend API","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://triliumnext.github.io/Notes/Script%20API/interfaces/Backend_Script_API.Api.html"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_vElnKeDNPSVl","title":"Logging","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Logging"},{"name":"iconClass","value":"bx bx-terminal","type":"label"}]}]}] \ No newline at end of file +[{"id":"_help_BOCnjTMBCoxW","title":"Feature Highlights","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Feature Highlights"},{"name":"iconClass","value":"bx bx-star","type":"label"}]},{"id":"_help_Otzi9La2YAUX","title":"Installation & Setup","type":"book","attributes":[{"name":"iconClass","value":"bx bx-cog","type":"label"}],"children":[{"id":"_help_poXkQfguuA0U","title":"Desktop Installation","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Desktop Installation"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_nRqcgfTb97uV","title":"Using the desktop application as a server","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Desktop Installation/Using the desktop application "},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_WOcw2SLH6tbX","title":"Server Installation","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_Dgg7bR3b6K9j","title":"1. Installing the server","type":"book","attributes":[{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_3tW6mORuTHnB","title":"Packaged version for Linux","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Packaged version for Linux"},{"name":"iconClass","value":"bx bxl-tux","type":"label"}]},{"id":"_help_rWX5eY045zbE","title":"Using Docker","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Using Docker"},{"name":"iconClass","value":"bx bxl-docker","type":"label"}]},{"id":"_help_moVgBcoxE3EK","title":"On NixOS","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/On NixOS"},{"name":"iconClass","value":"bx bxl-tux","type":"label"}]},{"id":"_help_J1Bb6lVlwU5T","title":"Manually","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Manually"},{"name":"iconClass","value":"bx bx-code-alt","type":"label"}]},{"id":"_help_DCmT6e7clMoP","title":"Using Kubernetes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Using Kubernetes"},{"name":"iconClass","value":"bx bxl-kubernetes","type":"label"}]},{"id":"_help_klCWNks3ReaQ","title":"Multiple server instances","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Multiple server instances"},{"name":"iconClass","value":"bx bxs-user-account","type":"label"}]}]},{"id":"_help_vcjrb3VVYPZI","title":"2. Reverse proxy","type":"book","attributes":[{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_ud6MShXL4WpO","title":"Nginx","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/2. Reverse proxy/Nginx"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_fDLvzOx29Pfg","title":"Apache using Docker","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/2. Reverse proxy/Apache using Docker"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_LLzSMXACKhUs","title":"Trusted proxy","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/2. Reverse proxy/Trusted proxy"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_l2VkvOwUNfZj","title":"HTTPS (TLS)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/HTTPS (TLS)"},{"name":"iconClass","value":"bx bx-lock-alt","type":"label"}]},{"id":"_help_0hzsNCP31IAB","title":"Authentication","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/Authentication"},{"name":"iconClass","value":"bx bx-user","type":"label"}]},{"id":"_help_7DAiwaf8Z7Rz","title":"Multi-Factor Authentication","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Server Installation/Multi-Factor Authentication"},{"name":"iconClass","value":"bx bx-stopwatch","type":"label"}]}]},{"id":"_help_cbkrhQjrkKrh","title":"Synchronization","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Synchronization"},{"name":"iconClass","value":"bx bx-sync","type":"label"}]},{"id":"_help_RDslemsQ6gCp","title":"Mobile Frontend","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Mobile Frontend"},{"name":"iconClass","value":"bx bx-mobile-alt","type":"label"}]},{"id":"_help_MtPxeAWVAzMg","title":"Web Clipper","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Web Clipper"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_n1lujUxCwipy","title":"Upgrading TriliumNext","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Upgrading TriliumNext"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_ODY7qQn5m2FT","title":"Backup","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Backup"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_tAassRL4RSQL","title":"Data directory","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Installation & Setup/Data directory"},{"name":"iconClass","value":"bx bx-folder-open","type":"label"}]}]},{"id":"_help_gh7bpGYxajRS","title":"Basic Concepts and Features","type":"book","attributes":[{"name":"iconClass","value":"bx bx-help-circle","type":"label"}],"children":[{"id":"_help_Vc8PjrjAGuOp","title":"UI Elements","type":"book","attributes":[{"name":"iconClass","value":"bx bx-window-alt","type":"label"}],"children":[{"id":"_help_x0JgW8UqGXvq","title":"Vertical and horizontal layout","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Vertical and horizontal layout"},{"name":"iconClass","value":"bx bxs-layout","type":"label"}]},{"id":"_help_x3i7MxGccDuM","title":"Global menu","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Global menu"},{"name":"iconClass","value":"bx bx-menu","type":"label"}]},{"id":"_help_oPVyFC7WL2Lp","title":"Note Tree","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree"},{"name":"iconClass","value":"bx bxs-tree-alt","type":"label"}],"children":[{"id":"_help_YtSN43OrfzaA","title":"Note tree contextual menu","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Note tree contextual menu"},{"name":"iconClass","value":"bx bx-menu","type":"label"}]},{"id":"_help_yTjUdsOi4CIE","title":"Multiple selection","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Multiple selection"},{"name":"iconClass","value":"bx bx-list-plus","type":"label"}]},{"id":"_help_DvdZhoQZY9Yd","title":"Keyboard shortcuts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tree/Keyboard shortcuts"},{"name":"iconClass","value":"bx bxs-keyboard","type":"label"}]}]},{"id":"_help_BlN9DFI679QC","title":"Ribbon","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Ribbon"},{"name":"iconClass","value":"bx bx-dots-horizontal","type":"label"}]},{"id":"_help_3seOhtN8uLIY","title":"Tabs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Tabs"},{"name":"iconClass","value":"bx bx-dock-top","type":"label"}]},{"id":"_help_xYmIYSP6wE3F","title":"Launch Bar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Launch Bar"},{"name":"iconClass","value":"bx bx-sidebar","type":"label"}]},{"id":"_help_8YBEPzcpUgxw","title":"Note buttons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note buttons"},{"name":"iconClass","value":"bx bx-dots-vertical-rounded","type":"label"}]},{"id":"_help_4TIF1oA4VQRO","title":"Options","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Options"},{"name":"iconClass","value":"bx bx-cog","type":"label"}]},{"id":"_help_luNhaphA37EO","title":"Split View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Split View"},{"name":"iconClass","value":"bx bx-dock-right","type":"label"}]},{"id":"_help_XpOYSgsLkTJy","title":"Floating buttons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Floating buttons"},{"name":"iconClass","value":"bx bx-rectangle","type":"label"}]},{"id":"_help_RnaPdbciOfeq","title":"Right Sidebar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Right Sidebar"},{"name":"iconClass","value":"bx bxs-dock-right","type":"label"}]},{"id":"_help_r5JGHN99bVKn","title":"Recent Changes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Recent Changes"},{"name":"iconClass","value":"bx bx-history","type":"label"}]},{"id":"_help_ny318J39E5Z0","title":"Zoom","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Zoom"},{"name":"iconClass","value":"bx bx-zoom-in","type":"label"}]},{"id":"_help_ZjLYv08Rp3qC","title":"Quick edit","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Quick edit"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_lgKX7r3aL30x","title":"Note Tooltip","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/UI Elements/Note Tooltip"},{"name":"iconClass","value":"bx bx-message-detail","type":"label"}]}]},{"id":"_help_BFs8mudNFgCS","title":"Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes"},{"name":"iconClass","value":"bx bx-notepad","type":"label"}],"children":[{"id":"_help_p9kXRFAkwN4o","title":"Note Icons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Note Icons"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]},{"id":"_help_0vhv7lsOLy82","title":"Attachments","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Attachments"},{"name":"iconClass","value":"bx bx-paperclip","type":"label"}]},{"id":"_help_IakOLONlIfGI","title":"Cloning Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Cloning Notes"},{"name":"iconClass","value":"bx bx-duplicate","type":"label"}],"children":[{"id":"_help_TBwsyfadTA18","title":"Branch prefix","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Cloning Notes/Branch prefix"},{"name":"iconClass","value":"bx bx-rename","type":"label"}]}]},{"id":"_help_bwg0e8ewQMak","title":"Protected Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Protected Notes"},{"name":"iconClass","value":"bx bx-lock-alt","type":"label"}]},{"id":"_help_MKmLg5x6xkor","title":"Archived Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Archived Notes"},{"name":"iconClass","value":"bx bx-box","type":"label"}]},{"id":"_help_vZWERwf8U3nx","title":"Note Revisions","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Note Revisions"},{"name":"iconClass","value":"bx bx-history","type":"label"}]},{"id":"_help_aGlEvb9hyDhS","title":"Sorting Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Sorting Notes"},{"name":"iconClass","value":"bx bx-sort-up","type":"label"}]},{"id":"_help_NRnIZmSMc5sj","title":"Printing & Exporting as PDF","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Printing & Exporting as PDF"},{"name":"iconClass","value":"bx bx-printer","type":"label"}]},{"id":"_help_CoFPLs3dRlXc","title":"Read-Only Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Read-Only Notes"},{"name":"iconClass","value":"bx bx-edit-alt","type":"label"}]},{"id":"_help_0ESUbbAxVnoK","title":"Note List","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Notes/Note List"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]}]},{"id":"_help_wArbEsdSae6g","title":"Navigation","type":"book","attributes":[{"name":"iconClass","value":"bx bx-navigation","type":"label"}],"children":[{"id":"_help_kBrnXNG3Hplm","title":"Tree Concepts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Tree Concepts"},{"name":"iconClass","value":"bx bx-pyramid","type":"label"}]},{"id":"_help_MMiBEQljMQh2","title":"Note Navigation","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Note Navigation"},{"name":"iconClass","value":"bx bxs-navigation","type":"label"}]},{"id":"_help_Ms1nauBra7gq","title":"Quick search","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Quick search"},{"name":"iconClass","value":"bx bx-search-alt-2","type":"label"}]},{"id":"_help_F1r9QtzQLZqm","title":"Jump to...","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Jump to"},{"name":"iconClass","value":"bx bx-send","type":"label"}]},{"id":"_help_eIg8jdvaoNNd","title":"Search","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Search"},{"name":"iconClass","value":"bx bx-search-alt-2","type":"label"}]},{"id":"_help_u3YFHC9tQlpm","title":"Bookmarks","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Bookmarks"},{"name":"iconClass","value":"bx bx-bookmarks","type":"label"}]},{"id":"_help_OR8WJ7Iz9K4U","title":"Note Hoisting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Note Hoisting"},{"name":"iconClass","value":"bx bxs-chevrons-up","type":"label"}]},{"id":"_help_ZjLYv08Rp3qC","title":"Quick edit","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Quick edit.clone"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_9sRHySam5fXb","title":"Workspaces","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Workspaces"},{"name":"iconClass","value":"bx bx-door-open","type":"label"}]},{"id":"_help_xWtq5NUHOwql","title":"Similar Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Similar Notes"},{"name":"iconClass","value":"bx bx-bar-chart","type":"label"}]},{"id":"_help_McngOG2jbUWX","title":"Search in note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Navigation/Search in note"},{"name":"iconClass","value":"bx bx-search-alt-2","type":"label"}]}]},{"id":"_help_A9Oc6YKKc65v","title":"Keyboard Shortcuts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Keyboard Shortcuts"},{"name":"iconClass","value":"bx bxs-keyboard","type":"label"}]},{"id":"_help_Wy267RK4M69c","title":"Themes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Themes"},{"name":"iconClass","value":"bx bx-palette","type":"label"}],"children":[{"id":"_help_VbjZvtUek0Ln","title":"Theme Gallery","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Themes/Theme Gallery"},{"name":"iconClass","value":"bx bx-book-reader","type":"label"}]}]},{"id":"_help_mHbBMPDPkVV5","title":"Import & Export","type":"book","attributes":[{"name":"iconClass","value":"bx bx-import","type":"label"}],"children":[{"id":"_help_Oau6X9rCuegd","title":"Markdown","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/Markdown"},{"name":"iconClass","value":"bx bxl-markdown","type":"label"}],"children":[{"id":"_help_rJ9grSgoExl9","title":"Supported syntax","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/Markdown/Supported syntax"},{"name":"iconClass","value":"bx bx-code-alt","type":"label"}]}]},{"id":"_help_syuSEKf2rUGr","title":"Evernote","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/Evernote"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_GnhlmrATVqcH","title":"OneNote","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Import & Export/OneNote"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_rC3pL2aptaRE","title":"Zen mode","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Basic Concepts and Features/Zen mode"},{"name":"iconClass","value":"bx bxs-yin-yang","type":"label"}]}]},{"id":"_help_s3YCWHBfmYuM","title":"Quick Start","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Quick Start"},{"name":"iconClass","value":"bx bx-run","type":"label"}]},{"id":"_help_i6dbnitykE5D","title":"FAQ","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/FAQ"},{"name":"iconClass","value":"bx bx-question-mark","type":"label"}]},{"id":"_help_KSZ04uQ2D1St","title":"Note Types","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types"},{"name":"iconClass","value":"bx bx-edit","type":"label"}],"children":[{"id":"_help_iPIMuisry3hd","title":"Text","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text"},{"name":"iconClass","value":"bx bx-note","type":"label"}],"children":[{"id":"_help_NwBbFdNZ9h7O","title":"Block quotes & admonitions","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Block quotes & admonitions"},{"name":"iconClass","value":"bx bx-info-circle","type":"label"}]},{"id":"_help_oSuaNgyyKnhu","title":"Bookmarks","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Bookmarks"},{"name":"iconClass","value":"bx bx-bookmark","type":"label"}]},{"id":"_help_veGu4faJErEM","title":"Content language & Right-to-left support","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Content language & Right-to-le"},{"name":"iconClass","value":"bx bx-align-right","type":"label"}]},{"id":"_help_2x0ZAX9ePtzV","title":"Cut to subnote","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Cut to subnote"},{"name":"iconClass","value":"bx bx-cut","type":"label"}]},{"id":"_help_UYuUB1ZekNQU","title":"Developer-specific formatting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Developer-specific formatting"},{"name":"iconClass","value":"bx bx-code-alt","type":"label"}],"children":[{"id":"_help_QxEyIjRBizuC","title":"Code blocks","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Developer-specific formatting/Code blocks"},{"name":"iconClass","value":"bx bx-code","type":"label"}]}]},{"id":"_help_AgjCISero73a","title":"Footnotes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Footnotes"},{"name":"iconClass","value":"bx bx-bracket","type":"label"}]},{"id":"_help_nRhnJkTT8cPs","title":"Formatting toolbar","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Formatting toolbar"},{"name":"iconClass","value":"bx bx-text","type":"label"}]},{"id":"_help_Gr6xFaF6ioJ5","title":"General formatting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/General formatting"},{"name":"iconClass","value":"bx bx-bold","type":"label"}]},{"id":"_help_AxshuNRegLAv","title":"Highlights list","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Highlights list"},{"name":"iconClass","value":"bx bx-highlight","type":"label"}]},{"id":"_help_mT0HEkOsz6i1","title":"Images","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Images"},{"name":"iconClass","value":"bx bx-image-alt","type":"label"}],"children":[{"id":"_help_0Ofbk1aSuVRu","title":"Image references","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Images/Image references"},{"name":"iconClass","value":"bx bxs-file-image","type":"label"}]}]},{"id":"_help_nBAXQFj20hS1","title":"Include Note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Include Note"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_CohkqWQC1iBv","title":"Insert buttons","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Insert buttons"},{"name":"iconClass","value":"bx bx-plus","type":"label"}]},{"id":"_help_oiVPnW8QfnvS","title":"Keyboard shortcuts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Keyboard shortcuts"},{"name":"iconClass","value":"bx bxs-keyboard","type":"label"}]},{"id":"_help_QEAPj01N5f7w","title":"Links","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Links"},{"name":"iconClass","value":"bx bx-link-alt","type":"label"}],"children":[{"id":"_help_3IDVtesTQ8ds","title":"External links","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Links/External links"},{"name":"iconClass","value":"bx bx-link-external","type":"label"}]},{"id":"_help_hrZ1D00cLbal","title":"Internal (reference) links","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Links/Internal (reference) links"},{"name":"iconClass","value":"bx bx-link","type":"label"}]}]},{"id":"_help_S6Xx8QIWTV66","title":"Lists","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Lists"},{"name":"iconClass","value":"bx bx-list-ul","type":"label"}]},{"id":"_help_QrtTYPmdd1qq","title":"Markdown-like formatting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Markdown-like formatting"},{"name":"iconClass","value":"bx bxl-markdown","type":"label"}]},{"id":"_help_YfYAtQBcfo5V","title":"Math Equations","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Math Equations"},{"name":"iconClass","value":"bx bx-math","type":"label"}]},{"id":"_help_dEHYtoWWi8ct","title":"Other features","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Other features"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]},{"id":"_help_gLt3vA97tMcp","title":"Premium features","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Premium features"},{"name":"iconClass","value":"bx bx-star","type":"label"}],"children":[{"id":"_help_ZlN4nump6EbW","title":"Slash Commands","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Premium features/Slash Commands"},{"name":"iconClass","value":"bx bx-menu","type":"label"}]},{"id":"_help_pwc194wlRzcH","title":"Text Snippets","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Premium features/Text Snippets"},{"name":"iconClass","value":"bx bx-align-left","type":"label"}]}]},{"id":"_help_BFvAtE74rbP6","title":"Table of contents","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Table of contents"},{"name":"iconClass","value":"bx bx-heading","type":"label"}]},{"id":"_help_NdowYOC1GFKS","title":"Tables","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Text/Tables"},{"name":"iconClass","value":"bx bx-table","type":"label"}]}]},{"id":"_help_6f9hih2hXXZk","title":"Code","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Code"},{"name":"iconClass","value":"bx bx-code","type":"label"}]},{"id":"_help_m523cpzocqaD","title":"Saved Search","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Saved Search"},{"name":"iconClass","value":"bx bx-file-find","type":"label"}]},{"id":"_help_iRwzGnHPzonm","title":"Relation Map","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Relation Map"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]},{"id":"_help_bdUJEHsAPYQR","title":"Note Map","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Note Map"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]},{"id":"_help_HcABDtFCkbFN","title":"Render Note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Render Note"},{"name":"iconClass","value":"bx bx-extension","type":"label"}]},{"id":"_help_GTwFsgaA0lCt","title":"Collections","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Collections"},{"name":"iconClass","value":"bx bx-book","type":"label"}],"children":[{"id":"_help_xWbu3jpNWapp","title":"Calendar View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Collections/Calendar View"},{"name":"iconClass","value":"bx bx-calendar","type":"label"}]},{"id":"_help_81SGnPGMk7Xc","title":"Geo Map View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Collections/Geo Map View"},{"name":"iconClass","value":"bx bx-map-alt","type":"label"}]},{"id":"_help_8QqnMzx393bx","title":"Grid View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Collections/Grid View"},{"name":"iconClass","value":"bx bxs-grid","type":"label"}]},{"id":"_help_mULW0Q3VojwY","title":"List View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Collections/List View"},{"name":"iconClass","value":"bx bx-list-ul","type":"label"}]},{"id":"_help_2FvYrpmOXm29","title":"Table View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Collections/Table View"},{"name":"iconClass","value":"bx bx-table","type":"label"}]},{"id":"_help_CtBQqbwXDx1w","title":"Board View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Collections/Board View"},{"name":"iconClass","value":"bx bx-columns","type":"label"}]},{"id":"_help_zP3PMqaG71Ct","title":"Presentation View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Collections/Presentation View"},{"name":"iconClass","value":"bx bx-slideshow","type":"label"}]}]},{"id":"_help_s1aBHPd79XYj","title":"Mermaid Diagrams","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Mermaid Diagrams"},{"name":"iconClass","value":"bx bx-selection","type":"label"}],"children":[{"id":"_help_RH6yLjjWJHof","title":"ELK layout","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Mermaid Diagrams/ELK layout"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]}]},{"id":"_help_grjYqerjn243","title":"Canvas","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Canvas"},{"name":"iconClass","value":"bx bx-pen","type":"label"}]},{"id":"_help_1vHRoWCEjj0L","title":"Web View","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Web View"},{"name":"iconClass","value":"bx bx-globe-alt","type":"label"}]},{"id":"_help_gBbsAeiuUxI5","title":"Mind Map","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/Mind Map"},{"name":"iconClass","value":"bx bx-sitemap","type":"label"}]},{"id":"_help_W8vYD3Q1zjCR","title":"File","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Note Types/File"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_BgmBlOIl72jZ","title":"Troubleshooting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting"},{"name":"iconClass","value":"bx bx-bug","type":"label"}],"children":[{"id":"_help_wy8So3yZZlH9","title":"Reporting issues","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Reporting issues"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_x59R8J8KV5Bp","title":"Anonymized Database","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Anonymized Database"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_qzNzp9LYQyPT","title":"Error logs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Error logs"},{"name":"iconClass","value":"bx bx-comment-error","type":"label"}],"children":[{"id":"_help_bnyigUA2UK7s","title":"Backend (server) logs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Error logs/Backend (server) logs"},{"name":"iconClass","value":"bx bx-server","type":"label"}]},{"id":"_help_9yEHzMyFirZR","title":"Frontend logs","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Error logs/Frontend logs"},{"name":"iconClass","value":"bx bx-window-alt","type":"label"}]}]},{"id":"_help_vdlYGAcpXAgc","title":"Synchronization fails with 504 Gateway Timeout","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Synchronization fails with 504"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_s8alTXmpFR61","title":"Refreshing the application","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Troubleshooting/Refreshing the application"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_pKK96zzmvBGf","title":"Theme development","type":"book","attributes":[{"name":"iconClass","value":"bx bx-palette","type":"label"}],"children":[{"id":"_help_7NfNr5pZpVKV","title":"Creating a custom theme","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Creating a custom theme"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_WFGzWeUK6arS","title":"Customize the Next theme","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Customize the Next theme"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_WN5z4M8ASACJ","title":"Reference","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Reference"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_AlhDUqhENtH7","title":"Custom app-wide CSS","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Theme development/Custom app-wide CSS"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_tC7s2alapj8V","title":"Advanced Usage","type":"book","attributes":[{"name":"iconClass","value":"bx bx-rocket","type":"label"}],"children":[{"id":"_help_zEY4DaJG4YT5","title":"Attributes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes"},{"name":"iconClass","value":"bx bx-list-check","type":"label"}],"children":[{"id":"_help_HI6GBBIduIgv","title":"Labels","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Labels"},{"name":"iconClass","value":"bx bx-hash","type":"label"}]},{"id":"_help_Cq5X6iKQop6R","title":"Relations","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Relations"},{"name":"iconClass","value":"bx bx-transfer","type":"label"}]},{"id":"_help_bwZpz2ajCEwO","title":"Attribute Inheritance","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Attribute Inheritance"},{"name":"iconClass","value":"bx bx-list-plus","type":"label"}]},{"id":"_help_OFXdgB2nNk1F","title":"Promoted Attributes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Attributes/Promoted Attributes"},{"name":"iconClass","value":"bx bx-table","type":"label"}]}]},{"id":"_help_KC1HB96bqqHX","title":"Templates","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Templates"},{"name":"iconClass","value":"bx bx-copy","type":"label"}]},{"id":"_help_BCkXAVs63Ttv","title":"Note Map (Link map, Tree map)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Note Map (Link map, Tree map)"},{"name":"iconClass","value":"bx bxs-network-chart","type":"label"}]},{"id":"_help_R9pX4DGra2Vt","title":"Sharing","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Sharing"},{"name":"iconClass","value":"bx bx-share-alt","type":"label"}],"children":[{"id":"_help_Qjt68inQ2bRj","title":"Serving directly the content of a note","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Sharing/Serving directly the content o"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_5668rwcirq1t","title":"Advanced Showcases","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_l0tKav7yLHGF","title":"Day Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases/Day Notes"},{"name":"iconClass","value":"bx bx-calendar","type":"label"}]},{"id":"_help_R7abl2fc6Mxi","title":"Weight Tracker","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases/Weight Tracker"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_xYjQUYhpbUEW","title":"Task Manager","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Advanced Showcases/Task Manager"},{"name":"iconClass","value":"bx bx-calendar-check","type":"label"}]}]},{"id":"_help_J5Ex1ZrMbyJ6","title":"Custom Request Handler","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Custom Request Handler"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_d3fAXQ2diepH","title":"Custom Resource Providers","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Custom Resource Providers"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_pgxEVkzLl1OP","title":"ETAPI (REST API)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/ETAPI (REST API)"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_9qPsTWBorUhQ","title":"API Reference","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"/etapi/docs"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_47ZrP6FNuoG8","title":"Default Note Title","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Default Note Title"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_wX4HbRucYSDD","title":"Database","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database"},{"name":"iconClass","value":"bx bx-data","type":"label"}],"children":[{"id":"_help_oyIAJ9PvvwHX","title":"Manually altering the database","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database/Manually altering the database"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_YKWqdJhzi2VY","title":"SQL Console","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database/Manually altering the database/SQL Console"},{"name":"iconClass","value":"bx bx-data","type":"label"}]}]},{"id":"_help_6tZeKvSHEUiB","title":"Demo Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Database/Demo Notes"},{"name":"iconClass","value":"bx bx-package","type":"label"}]}]},{"id":"_help_Gzjqa934BdH4","title":"Configuration (config.ini or environment variables)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Configuration (config.ini or e"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_c5xB8m4g2IY6","title":"Trilium instance","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Configuration (config.ini or environment variables)/Trilium instance"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_LWtBjFej3wX3","title":"Cross-Origin Resource Sharing (CORS)","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Configuration (config.ini or environment variables)/Cross-Origin Resource Sharing "},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_ivYnonVFBxbQ","title":"Bulk Actions","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Bulk Actions"},{"name":"iconClass","value":"bx bx-list-plus","type":"label"}]},{"id":"_help_4FahAwuGTAwC","title":"Note source","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Note source"},{"name":"iconClass","value":"bx bx-code","type":"label"}]},{"id":"_help_1YeN2MzFUluU","title":"Technologies used","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used"},{"name":"iconClass","value":"bx bxs-component","type":"label"}],"children":[{"id":"_help_MI26XDLSAlCD","title":"CKEditor","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/CKEditor"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_N4IDkixaDG9C","title":"MindElixir","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/MindElixir"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_H0mM1lTxF9JI","title":"Excalidraw","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/Excalidraw"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_MQHyy2dIFgxS","title":"Leaflet","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Technologies used/Leaflet"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_m1lbrzyKDaRB","title":"Note ID","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Note ID"},{"name":"iconClass","value":"bx bx-hash","type":"label"}]},{"id":"_help_0vTSyvhPTAOz","title":"Internal API","type":"book","attributes":[{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_z8O2VG4ZZJD7","title":"API Reference","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"/api/docs"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_2mUhVmZK8RF3","title":"Hidden Notes","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Hidden Notes"},{"name":"iconClass","value":"bx bx-hide","type":"label"}]},{"id":"_help_uYF7pmepw27K","title":"Metrics","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Advanced Usage/Metrics"},{"name":"iconClass","value":"bx bxs-data","type":"label"}],"children":[{"id":"_help_bOP3TB56fL1V","title":"grafana-dashboard.json","type":"doc","attributes":[{"name":"iconClass","value":"bx bx-file","type":"label"}]}]}]},{"id":"_help_LMAv4Uy3Wk6J","title":"AI","type":"book","attributes":[{"name":"iconClass","value":"bx bx-bot","type":"label"}],"children":[{"id":"_help_GBBMSlVSOIGP","title":"Introduction","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI/Introduction"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_WkM7gsEUyCXs","title":"AI Provider Information","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI/AI Provider Information"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_7EdTxPADv95W","title":"Ollama","type":"book","attributes":[{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_vvUCN7FDkq7G","title":"Installing Ollama","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI/AI Provider Information/Ollama/Installing Ollama"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_ZavFigBX9AwP","title":"OpenAI","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI/AI Provider Information/OpenAI"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_e0lkirXEiSNc","title":"Anthropic","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/AI/AI Provider Information/Anthropic"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]}]},{"id":"_help_CdNpE2pqjmI6","title":"Scripting","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting"},{"name":"iconClass","value":"bx bxs-file-js","type":"label"}],"children":[{"id":"_help_yIhgI5H7A2Sm","title":"Frontend Basics","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Frontend Basics"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_es8OU2GuguFU","title":"Examples","type":"book","attributes":[{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_TjLYAo3JMO8X","title":"\"New Task\" launcher button","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Examples/New Task launcher button"},{"name":"iconClass","value":"bx bx-task","type":"label"}]},{"id":"_help_7kZPMD0uFwkH","title":"Downloading responses from Google Forms","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Examples/Downloading responses from Goo"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_DL92EjAaXT26","title":"Using promoted attributes to configure scripts","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Examples/Using promoted attributes to c"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_GPERMystNGTB","title":"Events","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Events"},{"name":"iconClass","value":"bx bx-rss","type":"label"}]},{"id":"_help_MgibgPcfeuGz","title":"Custom Widgets","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Custom Widgets"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_YNxAqkI5Kg1M","title":"Word count widget","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Custom Widgets/Word count widget"},{"name":"iconClass","value":"bx bx-file","type":"label"}]},{"id":"_help_SynTBQiBsdYJ","title":"Widget Basics","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Custom Widgets/Widget Basics"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_GLks18SNjxmC","title":"Script API","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Script API"},{"name":"iconClass","value":"bx bx-file","type":"label"}],"children":[{"id":"_help_Q2z6av6JZVWm","title":"Frontend API","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://triliumnext.github.io/Notes/Script%20API/interfaces/Frontend_Script_API.Api.html"},{"name":"iconClass","value":"bx bx-folder","type":"label"}],"children":[{"id":"_help_habiZ3HU8Kw8","title":"FNote","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://triliumnext.github.io/Notes/Script%20API/classes/Frontend_Script_API.FNote.html"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_MEtfsqa5VwNi","title":"Backend API","type":"webView","attributes":[{"type":"label","name":"webViewSrc","value":"https://triliumnext.github.io/Notes/Script%20API/interfaces/Backend_Script_API.Api.html"},{"name":"iconClass","value":"bx bx-file","type":"label"}]}]},{"id":"_help_vElnKeDNPSVl","title":"Logging","type":"doc","attributes":[{"type":"label","name":"docName","value":"User Guide/User Guide/Scripting/Logging"},{"name":"iconClass","value":"bx bx-terminal","type":"label"}]}]}] \ No newline at end of file diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Attributes/Labels.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Attributes/Labels.html index d3d0f7e8d..0edc5714b 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Attributes/Labels.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Attributes/Labels.html @@ -302,7 +302,9 @@ color defines color of the note in note tree, links etc. Use any valid CSS color - value like 'red' or #a13d5f + value like 'red' or #a13d5f +
Note: this color may be automatically adjusted when displayed to ensure + sufficient contrast with the background. keyboardShortcut diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes/1_Export as PDF_image.png b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes/1_Printing & Exporting as PD.png similarity index 100% rename from apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes/1_Export as PDF_image.png rename to apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes/1_Printing & Exporting as PD.png diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes/Export as PDF.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes/Export as PDF.html deleted file mode 100644 index 491c4ad6e..000000000 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes/Export as PDF.html +++ /dev/null @@ -1,42 +0,0 @@ -

- -

-

Screenshot of the note contextual menu indicating the “Export as PDF” - option.

-

On the desktop application of Trilium it is possible to export a note - as PDF. On the server or PWA (mobile), the option is not available due - to technical constraints and it will be hidden.

-

To print a note, select the - button to the right of the note and select Export as PDF.

-

Afterwards you will be prompted to select where to save the PDF file.

-

Automatic opening of the file

-

When the PDF is exported, it is automatically opened with the system default - application for easy preview.

-

Note that if you are using Linux with the GNOME desktop environment, sometimes - the default application might seem incorrect (such as opening in GIMP). - This is because it uses Gnome's “Recommended applications” list.

-

To solve this, you can change the recommended application for PDFs via - this command line. First, list the available applications via gio mime application/pdf and - then set the desired one. For example to use GNOME's Evince:

gio mime application/pdf
-

Reporting issues with the rendering

-

Should you encounter any visual issues in the resulting PDF file (e.g. - a table does not fit properly, there is cut off text, etc.) feel free to - report the issue. In this case, it's best to offer a sample note (click - on the - button, select Export note → This note and all of its descendants → HTML - in ZIP archive). Make sure not to accidentally leak any personal information.

-

Landscape mode

-

When exporting to PDF, there are no customizable settings such as page - orientation, size, etc. However, it is possible to specify a given note - to be printed as a PDF in landscape mode by adding the #printLandscape attribute - to it (see Attributes).

-

Page size

-

By default, the resulting PDF will be in Letter format. It is possible - to adjust it to another page size via the #printPageSize attribute, - with one of the following values: A0, A1, A2, A3, A4, A5, A6, Legal, Letter, Tabloid, Ledger.

-

Keyboard shortcut

-

It's possible to trigger the export to PDF from the keyboard by going - to Keyboard shortcuts in Options and assigning a key combination - for the exportAsPdf action.

\ No newline at end of file diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes/Export as PDF_image.png b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes/Printing & Exporting as PD.png similarity index 100% rename from apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes/Export as PDF_image.png rename to apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes/Printing & Exporting as PD.png diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes/Printing & Exporting as PDF.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes/Printing & Exporting as PDF.html new file mode 100644 index 000000000..5ecb0f510 --- /dev/null +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes/Printing & Exporting as PDF.html @@ -0,0 +1,111 @@ +
+ +
Screenshot of the note contextual menu indicating the “Export as PDF” + option.
+
+

Printing

+

This feature allows printing of notes. It works on both the desktop client, + but also on the web.

+

Note that not all note types are printable as of now. We do plan to increase + the coverage of supported note types in the future.

+

To print a note, select the + button to the right of the note and select Print note. Depending + on the size and type of the note, this can take up to a few seconds. Afterwards + you will be redirected to the system/browser printing dialog.

+ +

Reporting issues with the rendering

+

Should you encounter any visual issues in the resulting PDF file (e.g. + a table does not fit properly, there is cut off text, etc.) feel free to + report the issue. In this case, it's best to offer a sample note (click + on the + button, select Export note → This note and all of its descendants → HTML + in ZIP archive). Make sure not to accidentally leak any personal information.

+

Consider adjusting font sizes and using page breaks to + work around the layout.

+

Exporting as PDF

+

On the desktop application of Trilium it is possible to export a note + as PDF. On the server or PWA (mobile), the option is not available due + to technical constraints and it will be hidden.

+

To print a note, select the + button to the right of the note and select Export as PDF. Afterwards + you will be prompted to select where to save the PDF file.

+

Automatic opening of the file

+

When the PDF is exported, it is automatically opened with the system default + application for easy preview.

+

Note that if you are using Linux with the GNOME desktop environment, sometimes + the default application might seem incorrect (such as opening in GIMP). + This is because it uses Gnome's “Recommended applications” list.

+

To solve this, you can change the recommended application for PDFs via + this command line. First, list the available applications via gio mime application/pdf and + then set the desired one. For example to use GNOME's Evince:

gio mime application/pdf
+

Customizing exporting as PDF

+

When exporting to PDF, there are no customizable settings such as page + orientation, size. However, there are a few Attributes to + adjust some of the settings:

+
    +
  • To print in landscape mode instead of portrait (useful for big diagrams + or slides), add #printLandscape.
  • +
  • By default, the resulting PDF will be in Letter format. It is possible + to adjust it to another page size via the #printPageSize attribute, + with one of the following values: A0, A1, A2, A3, A4, A5, A6, Legal, Letter, Tabloid, Ledger.
  • +
+ +

Keyboard shortcut

+

It's possible to trigger both printing and export as PDF from the keyboard + by going to Keyboard shortcuts in Options and assigning a key combination + for:

+
    +
  • Print Active Note +
  • +
  • Export Active Note as PDF +
  • +
+

Constraints & limitations

+

Not all Note Types are + supported when printing, in which case the Print and Export as PDF options + will be disabled.

+
    +
  • For Code notes: +
      +
    • Line numbers are not printed.
    • +
    • Syntax highlighting is enabled, however a default theme (Visual Studio) + is enforced.
    • +
    +
  • +
  • For Collections: +
      +
    • Only Presentation View is + currently supported.
    • +
    • We plan to add support for all the collection types at some point.
    • +
    +
  • +
  • Using Custom app-wide CSS for + printing is not longer supported, due to a more stable but isolated mechanism. +
      +
    • We plan to introduce a new mechanism specifically for a print CSS.
    • +
    +
  • +
+

Under the hood

+

Both printing and exporting as PDF use the same mechanism: a note is rendered + individually in a separate webpage that is then sent to the browser or + the Electron application either for printing or exporting as PDF.

+

The webpage that renders a single note can actually be accessed in a web + browser. For example http://localhost:8080/#root/WWRGzqHUfRln/RRZsE9Al8AIZ?ntxId=0o4fzk becomes http://localhost:8080/?print#root/WWRGzqHUfRln/RRZsE9Al8AIZ.

+

Accessing the print note in a web browser allows for easy debugging to + understand why a particular note doesn't render well. The mechanism for + rendering is similar to the one used in Note List.

\ No newline at end of file diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Collections/Presentation View.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Collections/Presentation View.html index 1ce364410..ca888c9cf 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Collections/Presentation View.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/Collections/Presentation View.html @@ -6,33 +6,31 @@ within Trilium.

How it works

    -
  • Each slide is a child note of the collection.
  • -
  • The order of the child notes determines the order of the slides.
  • -
  • Unlike traditional presentation software, slides can be laid out both +
  • Each slide is a child note of the collection.
  • +
  • The order of the child notes determines the order of the slides.
  • +
  • Unlike traditional presentation software, slides can be laid out both horizontally and vertically (see belwo for more information).
  • -
  • Direct children will be laid out horizontally and the children of those - will be laid out vertically. Children deeper than two levels of nesting - are ignored.
  • +
  • Direct children will be laid out horizontally and the children of those + will be laid out vertically. Children deeper than two levels of nesting + are ignored.

Interaction and navigation

In the floating buttons section (top-right):

    -
  • Edit button to go to the corresponding note of the current slide.
  • -
  • Press Overview button (or the O key) to show a birds-eye view +
  • Edit button to go to the corresponding note of the current slide.
  • +
  • Press Overview button (or the O key) to show a birds-eye view of the slides. Press the button again to disable it.
  • -
  • Press the “Start presentation” button to show the presentation in full-screen.
  • +
  • Press the “Start presentation” button to show the presentation in full-screen.

The following keyboard shortcuts are supported:

    -
  • Press and (or H and L) to go +
  • Press and (or H and L) to go to the slide on the left or on the right (horizontal).
  • -
  • Press and  (or K and J) +
  • Press and  (or K and J) to go to the upward or downward slide (vertical).
  • -
  • Press Space and Shift + Space or  to go +
  • Press Space and Shift + Space or  to go to the next/previous slide in order.
  • -
  • And a few more, press ? to display a popup with all the supported +
  • And a few more, press ? to display a popup with all the supported keyboard combinations.

Vertical slides and nesting

@@ -42,15 +40,15 @@

This horizontal/vertical organization affects transitions (especially on the “slide” transition), however it is most noticeable in navigation.

    -
  • Pressing and will navigate through slides horizontally, +
  • Pressing and will navigate through slides horizontally, thus skipping vertical notes under the current slide. This is useful to - skip entire chapters/related slides. 
  • -
  • Pressing and will navigate through the vertical + skip entire chapters/related slides.
  • +
  • Pressing and will navigate through the vertical slides at the current level.
  • -
  • Pressing Space and Shift + Space will go to +
  • Pressing Space and Shift + Space will go to the next/previous slide in order, regardless of the direction. This is generally the key combination to use when presenting.
  • -
  • The arrows on the bottom-right of the slide will also reflect this navigation +
  • The arrows on the bottom-right of the slide will also reflect this navigation scheme.
@@ -62,19 +60,19 @@ slides.

In the following example, the note structure is as follows:

    -
  • Presentation collection +
  • Presentation collection
      -
    • Trilium Notes (demo page)
    • -
    • “Introduction” slide +
    • Trilium Notes (demo page)
    • +
    • “Introduction” slide
        -
      • “The challenge of personal knowledge management”
      • -
      • “Note-taking structures”
      • +
      • “The challenge of personal knowledge management”
      • +
      • “Note-taking structures”
    • -
    • “Demo & Feature highlights” slide +
    • “Demo & Feature highlights” slide
        -
      • “Really fast installation process”
      • -
      • Video slide
      • +
      • “Really fast installation process”
      • +
      • Video slide
    @@ -83,56 +81,54 @@

    Customization

    At collection level, it's possible to adjust:

      -
    • The theme of the entire presentation to one of the predefined themes by - going to the Ribbon and +
    • The theme of the entire presentation to one of the predefined themes by + going to the Ribbon and looking for the Collection Properties tab.
    • -
    • It's currently not possible to create custom themes, although it is planned.
    • -
    • Note that it is note possible to alter the CSS via Custom app-wide CSS because - the slides are rendered isolated (in a shadow DOM).
    • +
    • It's currently not possible to create custom themes, although it is planned.
    • +
    • Note that it is note possible to alter the CSS via Custom app-wide CSS because the + slides are rendered isolated (in a shadow DOM).

    At slide level:

      -
    • It's possible to adjust the background color of a slide by using the +
    • It's possible to adjust the background color of a slide by using the predefined promoted attributefor the color or manually setting #slide:background to + href="#root/_help_OFXdgB2nNk1F">predefined promoted attributefor the color or manually setting #slide:background to a hex color.
    • -
    • More complex backgrounds can be achieved via gradients. There's no UI +
    • More complex backgrounds can be achieved via gradients. There's no UI for it; it has to be set via #slide:background to a CSS gradient definition such as: linear-gradient(to bottom, #283b95, #17b2c3).

    Tips and tricks

      -
    • Text notes generally respect the formatting (bold, italic, foreground +
    • Text notes generally respect the formatting (bold, italic, foreground and background colors) and font size. Code blocks and tables also work.
    • -
    • Try using more than just text notes, the presentation uses the same mechanism - as shared notes and  +
    • Try using more than just text notes, the presentation uses the same mechanism + as shared notes and Note List so it should be able + to display Mermaid DiagramsNote List so it should be able to display Mermaid Diagrams,  - Canvas and Mind Map in - full-screen (without the interactivity). -
        -
      • Consider using a transparent background for Canvas, if - the slides have a custom background (go to the hamburger menu in the Canvas, - press the button select a custom color and write transparent).
      • -
      • -

        For Mermaid Diagrams, - some of them have a predefined background which can be changed via the - frontmatter. For example, for XY-charts:

        ---
        +    class="reference-link" href="#root/_help_grjYqerjn243">Canvas and Mind Map in
        +      full-screen (without the interactivity).
        +      
          +
        • +

          Consider using a transparent background for Canvas, if the slides have a custom + background (go to the hamburger menu in the Canvas, press the button select + a custom color and write transparent).

          +
        • +
        • +

          For Mermaid Diagrams, + some of them have a predefined background which can be changed via the + frontmatter. For example, for XY-charts:

          ---
           config:
               themeVariables:
                   xyChart:
                       backgroundColor: transparent
           ---
          -
        • -
      • +
      +

    Under the hood

    The Presentation view uses Reveal.js to diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Theme development/Custom app-wide CSS.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Theme development/Custom app-wide CSS.html index 8a5189927..f7d9fd394 100644 --- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Theme development/Custom app-wide CSS.html +++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Theme development/Custom app-wide CSS.html @@ -1,38 +1,37 @@

    It is possible to provide a CSS file to be used regardless of the theme set by the user.

    -
    - - - - - - - - - - - - - - - - - - - - - -
      
    - - Start by creating a new note and changing the note type to CSS
    - - In the ribbon, press the “Owned Attributes” section and type #appCss.
    - - Type the desired CSS.   -
    -
    Generally it's a good idea to append !important for the styles - that are being changed, in order to prevent other
    -
    + + + + + + + + + + + + + + + + + + + + + +
    + + Start by creating a new note and changing the note type to CSS
    + + In the ribbon, press the “Owned Attributes” section and type #appCss.
    + + Type the desired CSS.   +
    +
    Generally it's a good idea to append !important for the styles + that are being changed, in order to prevent other
    +

    Seeing the changes

    Adding a new app CSS note or modifying an existing one does not immediately apply changes. To see the changes, press Ctrl+Shift+R to refresh @@ -54,10 +53,9 @@ workspaces.

    To do so:

      -
    1. In the note with #workspace, add an inheritable attribute #cssClass(inheritable) with +
    2. In the note with #workspace, add an inheritable attribute #cssClass(inheritable) with a value that uniquely identifies the workspace (say my-workspace).
    3. -
    4. Anywhere in the note structure, create a CSS note with #appCss.
    5. +
    6. Anywhere in the note structure, create a CSS note with #appCss.

    Change the color of the icons in the Note Tree

    .fancytree-node.my-workspace.fancytree-custom-icon {
         color: #ff0000;
    @@ -73,8 +71,8 @@
       width="641" height="630">
     
    -
  1. Insert an image in any note and take the URL of the image.
  2. -
  3. Use the following CSS, adjusting the background-image and width and height to +
  4. Insert an image in any note and take the URL of the image.
  5. +
  6. Use the following CSS, adjusting the background-image and width and height to the desired values.
.note-split.my-workspace .scrolling-container:after {
     position: fixed;
@@ -94,5 +92,5 @@
 

Some parts of the application can't be styled directly via custom CSS because they are rendered in an isolated mode (shadow DOM), more specifically:

\ No newline at end of file diff --git a/docs/User Guide/!!!meta.json b/docs/User Guide/!!!meta.json index 3b295b082..1bd423c28 100644 --- a/docs/User Guide/!!!meta.json +++ b/docs/User Guide/!!!meta.json @@ -3472,7 +3472,7 @@ "BFs8mudNFgCS", "NRnIZmSMc5sj" ], - "title": "Export as PDF", + "title": "Printing & Exporting as PDF", "notePosition": 120, "prefix": null, "isExpanded": false, @@ -3503,13 +3503,62 @@ { "type": "label", "name": "iconClass", - "value": "bx bxs-file-pdf", + "value": "bx bx-printer", "isInheritable": false, "position": 30 + }, + { + "type": "relation", + "name": "internalLink", + "value": "CohkqWQC1iBv", + "isInheritable": false, + "position": 40 + }, + { + "type": "relation", + "name": "internalLink", + "value": "0ESUbbAxVnoK", + "isInheritable": false, + "position": 50 + }, + { + "type": "relation", + "name": "internalLink", + "value": "KSZ04uQ2D1St", + "isInheritable": false, + "position": 60 + }, + { + "type": "relation", + "name": "internalLink", + "value": "6f9hih2hXXZk", + "isInheritable": false, + "position": 70 + }, + { + "type": "relation", + "name": "internalLink", + "value": "AlhDUqhENtH7", + "isInheritable": false, + "position": 80 + }, + { + "type": "relation", + "name": "internalLink", + "value": "GTwFsgaA0lCt", + "isInheritable": false, + "position": 90 + }, + { + "type": "relation", + "name": "internalLink", + "value": "zP3PMqaG71Ct", + "isInheritable": false, + "position": 100 } ], "format": "markdown", - "dataFileName": "Export as PDF.md", + "dataFileName": "Printing & Exporting as PDF.md", "attachments": [ { "attachmentId": "NfSjRsArIQHy", @@ -3517,7 +3566,7 @@ "role": "image", "mime": "image/png", "position": 10, - "dataFileName": "Export as PDF_image.png" + "dataFileName": "Printing & Exporting as PD.png" }, { "attachmentId": "Om2EmdZr54vy", @@ -3525,7 +3574,7 @@ "role": "image", "mime": "image/png", "position": 10, - "dataFileName": "1_Export as PDF_image.png" + "dataFileName": "1_Printing & Exporting as PD.png" } ] }, @@ -8758,51 +8807,51 @@ "mime": "text/html", "attributes": [ { - "type": "label", - "name": "iconClass", - "value": "bx bx-slideshow", + "type": "relation", + "name": "internalLink", + "value": "BlN9DFI679QC", "isInheritable": false, "position": 10 }, { "type": "relation", "name": "internalLink", - "value": "BlN9DFI679QC", + "value": "OFXdgB2nNk1F", "isInheritable": false, "position": 20 }, { "type": "relation", "name": "internalLink", - "value": "OFXdgB2nNk1F", + "value": "R9pX4DGra2Vt", "isInheritable": false, "position": 30 }, { "type": "relation", "name": "internalLink", - "value": "R9pX4DGra2Vt", + "value": "0ESUbbAxVnoK", "isInheritable": false, "position": 40 }, { "type": "relation", "name": "internalLink", - "value": "0ESUbbAxVnoK", + "value": "grjYqerjn243", "isInheritable": false, "position": 50 }, { "type": "relation", "name": "internalLink", - "value": "s1aBHPd79XYj", + "value": "AlhDUqhENtH7", "isInheritable": false, "position": 60 }, { "type": "relation", "name": "internalLink", - "value": "grjYqerjn243", + "value": "s1aBHPd79XYj", "isInheritable": false, "position": 70 }, @@ -8814,11 +8863,11 @@ "position": 80 }, { - "type": "relation", - "name": "internalLink", - "value": "AlhDUqhENtH7", + "type": "label", + "name": "iconClass", + "value": "bx bx-slideshow", "isInheritable": false, - "position": 90 + "position": 10 } ], "format": "markdown", diff --git a/docs/User Guide/User Guide/Basic Concepts and Features/Notes/1_Export as PDF_image.png b/docs/User Guide/User Guide/Basic Concepts and Features/Notes/1_Printing & Exporting as PD.png similarity index 100% rename from docs/User Guide/User Guide/Basic Concepts and Features/Notes/1_Export as PDF_image.png rename to docs/User Guide/User Guide/Basic Concepts and Features/Notes/1_Printing & Exporting as PD.png diff --git a/docs/User Guide/User Guide/Basic Concepts and Features/Notes/Export as PDF.md b/docs/User Guide/User Guide/Basic Concepts and Features/Notes/Export as PDF.md deleted file mode 100644 index 20f5b7394..000000000 --- a/docs/User Guide/User Guide/Basic Concepts and Features/Notes/Export as PDF.md +++ /dev/null @@ -1,38 +0,0 @@ -# Export as PDF -![](Export%20as%20PDF_image.png) - -Screenshot of the note contextual menu indicating the “Export as PDF” option. - -On the desktop application of Trilium it is possible to export a note as PDF. On the server or PWA (mobile), the option is not available due to technical constraints and it will be hidden. - -To print a note, select the ![](1_Export%20as%20PDF_image.png) button to the right of the note and select _Export as PDF_. - -Afterwards you will be prompted to select where to save the PDF file. - -## Automatic opening of the file - -When the PDF is exported, it is automatically opened with the system default application for easy preview. - -Note that if you are using Linux with the GNOME desktop environment, sometimes the default application might seem incorrect (such as opening in GIMP). This is because it uses Gnome's “Recommended applications” list. - -To solve this, you can change the recommended application for PDFs via this command line. First, list the available applications via `gio mime application/pdf` and then set the desired one. For example to use GNOME's Evince: - -``` -gio mime application/pdf -``` - -## Reporting issues with the rendering - -Should you encounter any visual issues in the resulting PDF file (e.g. a table does not fit properly, there is cut off text, etc.) feel free to [report the issue](../../Troubleshooting/Reporting%20issues.md). In this case, it's best to offer a sample note (click on the ![](1_Export%20as%20PDF_image.png) button, select Export note → This note and all of its descendants → HTML in ZIP archive). Make sure not to accidentally leak any personal information. - -## Landscape mode - -When exporting to PDF, there are no customizable settings such as page orientation, size, etc. However, it is possible to specify a given note to be printed as a PDF in landscape mode by adding the `#printLandscape` attribute to it (see Attributes). - -## Page size - -By default, the resulting PDF will be in Letter format. It is possible to adjust it to another page size via the `#printPageSize` attribute, with one of the following values: `A0`, `A1`, `A2`, `A3`, `A4`, `A5`, `A6`, `Legal`, `Letter`, `Tabloid`, `Ledger`. - -## Keyboard shortcut - -It's possible to trigger the export to PDF from the keyboard by going to _Keyboard shortcuts_ in Options and assigning a key combination for the `exportAsPdf` action. \ No newline at end of file diff --git a/docs/User Guide/User Guide/Basic Concepts and Features/Notes/Export as PDF_image.png b/docs/User Guide/User Guide/Basic Concepts and Features/Notes/Printing & Exporting as PD.png similarity index 100% rename from docs/User Guide/User Guide/Basic Concepts and Features/Notes/Export as PDF_image.png rename to docs/User Guide/User Guide/Basic Concepts and Features/Notes/Printing & Exporting as PD.png diff --git a/docs/User Guide/User Guide/Basic Concepts and Features/Notes/Printing & Exporting as PDF.md b/docs/User Guide/User Guide/Basic Concepts and Features/Notes/Printing & Exporting as PDF.md new file mode 100644 index 000000000..928b134c6 --- /dev/null +++ b/docs/User Guide/User Guide/Basic Concepts and Features/Notes/Printing & Exporting as PDF.md @@ -0,0 +1,75 @@ +# Printing & Exporting as PDF +
Screenshot of the note contextual menu indicating the “Export as PDF” option.
+ +## Printing + +This feature allows printing of notes. It works on both the desktop client, but also on the web. + +Note that not all note types are printable as of now. We do plan to increase the coverage of supported note types in the future. + +To print a note, select the button to the right of the note and select _Print note_. Depending on the size and type of the note, this can take up to a few seconds. Afterwards you will be redirected to the system/browser printing dialog. + +> [!NOTE] +> Printing and exporting as PDF are not perfect. Due to technical limitations, and sometimes even browser glitches the text might appear cut off in some circumstances.  + +## Reporting issues with the rendering + +Should you encounter any visual issues in the resulting PDF file (e.g. a table does not fit properly, there is cut off text, etc.) feel free to [report the issue](../../Troubleshooting/Reporting%20issues.md). In this case, it's best to offer a sample note (click on the button, select Export note → This note and all of its descendants → HTML in ZIP archive). Make sure not to accidentally leak any personal information. + +Consider adjusting font sizes and using [page breaks](../../Note%20Types/Text/Insert%20buttons.md) to work around the layout. + +## Exporting as PDF + +On the desktop application of Trilium it is possible to export a note as PDF. On the server or PWA (mobile), the option is not available due to technical constraints and it will be hidden. + +To print a note, select the ![](1_Printing%20&%20Exporting%20as%20PD.png) button to the right of the note and select _Export as PDF_. Afterwards you will be prompted to select where to save the PDF file. + +### Automatic opening of the file + +When the PDF is exported, it is automatically opened with the system default application for easy preview. + +Note that if you are using Linux with the GNOME desktop environment, sometimes the default application might seem incorrect (such as opening in GIMP). This is because it uses Gnome's “Recommended applications” list. + +To solve this, you can change the recommended application for PDFs via this command line. First, list the available applications via `gio mime application/pdf` and then set the desired one. For example to use GNOME's Evince: + +``` +gio mime application/pdf +``` + +### Customizing exporting as PDF + +When exporting to PDF, there are no customizable settings such as page orientation, size. However, there are a few Attributes to adjust some of the settings: + +* To print in landscape mode instead of portrait (useful for big diagrams or slides), add `#printLandscape`. +* By default, the resulting PDF will be in Letter format. It is possible to adjust it to another page size via the `#printPageSize` attribute, with one of the following values: `A0`, `A1`, `A2`, `A3`, `A4`, `A5`, `A6`, `Legal`, `Letter`, `Tabloid`, `Ledger`. + +> [!NOTE] +> These options have no effect when used with the printing feature, since the user-defined settings are used instead. + +## Keyboard shortcut + +It's possible to trigger both printing and export as PDF from the keyboard by going to _Keyboard shortcuts_ in Options and assigning a key combination for: + +* _Print Active Note_ +* _Export Active Note as PDF_ + +## Constraints & limitations + +Not all Note Types are supported when printing, in which case the _Print_ and _Export as PDF_ options will be disabled. + +* For Code notes: + * Line numbers are not printed. + * Syntax highlighting is enabled, however a default theme (Visual Studio) is enforced. +* For Collections: + * Only Presentation View is currently supported. + * We plan to add support for all the collection types at some point. +* Using Custom app-wide CSS for printing is not longer supported, due to a more stable but isolated mechanism. + * We plan to introduce a new mechanism specifically for a print CSS. + +## Under the hood + +Both printing and exporting as PDF use the same mechanism: a note is rendered individually in a separate webpage that is then sent to the browser or the Electron application either for printing or exporting as PDF. + +The webpage that renders a single note can actually be accessed in a web browser. For example `http://localhost:8080/#root/WWRGzqHUfRln/RRZsE9Al8AIZ?ntxId=0o4fzk` becomes `http://localhost:8080/?print#root/WWRGzqHUfRln/RRZsE9Al8AIZ`. + +Accessing the print note in a web browser allows for easy debugging to understand why a particular note doesn't render well. The mechanism for rendering is similar to the one used in Note List. \ No newline at end of file diff --git a/docs/User Guide/User Guide/Feature Highlights.md b/docs/User Guide/User Guide/Feature Highlights.md index 7cedd84f1..77f30d590 100644 --- a/docs/User Guide/User Guide/Feature Highlights.md +++ b/docs/User Guide/User Guide/Feature Highlights.md @@ -19,7 +19,7 @@ This section presents the most important changes by version. For a full set of c * v0.92.4: * macOS binaries are now signed. * Text notes can now have adjustable Content language & Right-to-left support. - * Export as PDF + * Export as PDF * Zen mode * Calendar View, allowing notes to be displayed in a monthly grid based on start and end dates. * v0.91.5: diff --git a/docs/User Guide/User Guide/Note Types/Text/Insert buttons.md b/docs/User Guide/User Guide/Note Types/Text/Insert buttons.md index 6a161fadd..8d87e083c 100644 --- a/docs/User Guide/User Guide/Note Types/Text/Insert buttons.md +++ b/docs/User Guide/User Guide/Note Types/Text/Insert buttons.md @@ -53,7 +53,7 @@ Alternatively, it's possible to insert a horizontal ruler by typing `---`.
-Page breaks provide a way to force the next paragraph or block (table, image, etc.) to be displayed onto the next page when printing (either to a real printer to [when exporting to PDF](../../Basic%20Concepts%20and%20Features/Notes/Export%20as%20PDF.md)). +Page breaks provide a way to force the next paragraph or block (table, image, etc.) to be displayed onto the next page when printing (either to a real printer to [when exporting to PDF](../../Basic%20Concepts%20and%20Features/Notes/Printing%20%26%20Exporting%20as%20PDF.md)). Page breaks are marked in the editor with the words _Page break_, but they will not actually be shown when printed. From fffb8317cbd3e9f36ffdeb5f6e7317416cdcccd0 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 20 Oct 2025 14:24:14 +0300 Subject: [PATCH 35/36] fix(client/print): disable printing for unsupported collection types --- apps/client/src/widgets/ribbon/NoteActions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/widgets/ribbon/NoteActions.tsx b/apps/client/src/widgets/ribbon/NoteActions.tsx index 2fed2ea02..14adc6b4b 100644 --- a/apps/client/src/widgets/ribbon/NoteActions.tsx +++ b/apps/client/src/widgets/ribbon/NoteActions.tsx @@ -47,7 +47,7 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not const canBeConvertedToAttachment = note?.isEligibleForConversionToAttachment(); const isSearchable = ["text", "code", "book", "mindMap", "doc"].includes(note.type); const isInOptions = note.noteId.startsWith("_options"); - const isPrintable = ["text", "code", "book"].includes(note.type); + const isPrintable = ["text", "code"].includes(note.type) || (note.type === "book" && note.getLabelValue("viewType") === "presentation"); const isElectron = getIsElectron(); const isMac = getIsMac(); const hasSource = ["text", "code", "relationMap", "mermaid", "canvas", "mindMap"].includes(note.type); From 66896d645744979de8b4ebe386961f9c3d7a13fb Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 20 Oct 2025 14:27:35 +0300 Subject: [PATCH 36/36] fix(client/print): text notes affecting slides --- apps/client/src/print.css | 2 +- apps/client/src/print.tsx | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/client/src/print.css b/apps/client/src/print.css index b935d9eb7..9ccf54362 100644 --- a/apps/client/src/print.css +++ b/apps/client/src/print.css @@ -23,7 +23,7 @@ body { contain: none !important; } -.ck-content { +body[data-note-type="text"] .ck-content { font-size: var(--print-font-size); text-align: justify; } diff --git a/apps/client/src/print.tsx b/apps/client/src/print.tsx index e762f5781..de11d581a 100644 --- a/apps/client/src/print.tsx +++ b/apps/client/src/print.tsx @@ -33,6 +33,10 @@ function App({ note, noteId }: { note: FNote | null | undefined, noteId: string if (!note || !props) return + useLayoutEffect(() => { + document.body.dataset.noteType = note.type; + }, [ note ]); + return ( <> {note.type === "book"