diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index 3617c4c9f4b..7ff4b2c4091 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -13,6 +13,76 @@ import port from "@triliumnext/server/src/services/port.js"; import { join } from "path"; import { deferred, LOCALES } from "../../../packages/commons/src"; +/** + * Parses a `trilium://` protocol URL and returns the note ID, or null if the + * URL cannot be parsed. + * + * Supported formats: + * trilium://note/ + * trilium:// (legacy / shorthand) + */ +function parseTriliumUrl(rawUrl: string): string | null { + let parsed: URL; + try { + parsed = new URL(rawUrl); + } catch { + return null; + } + + if (parsed.protocol !== "trilium:") { + return null; + } + + // trilium://note/ → hostname = "note", pathname = "/" + if (parsed.hostname === "note") { + const noteId = parsed.pathname.replace(/^\/+/, "").trim(); + return noteId || null; + } + + // trilium:// → hostname = "" + const noteId = parsed.hostname.trim(); + return noteId || null; +} + +/** + * Extracts a `trilium://` URL from a process argv / commandLine array, or + * returns null if none is present. + */ +function extractTriliumUrlFromArgs(args: string[]): string | null { + for (const arg of args) { + if (arg.startsWith("trilium://")) { + return arg; + } + // --open-note= convenience flag + const m = arg.match(/^--open-note=(.+)$/); + if (m) { + return `trilium://note/${m[1]}`; + } + } + return null; +} + +/** + * Focuses the main window and navigates to the given note. + * Safe to call before the window is created; returns false if navigation was + * not possible (e.g. window not yet ready). + */ +function navigateToNote(noteId: string): boolean { + const win = windowService.getLastFocusedWindow() ?? windowService.getMainWindow(); + if (!win || win.isDestroyed()) { + return false; + } + + if (win.isMinimized()) { + win.restore(); + } + win.show(); + win.focus(); + + win.webContents.send("openInSameTab", noteId); + return true; +} + async function main() { const userDataPath = getUserData(); app.setPath("userData", userDataPath); @@ -24,6 +94,11 @@ async function main() { process.exit(0); } + // Register trilium:// as a custom URI scheme so external apps and the OS + // can launch Trilium and navigate directly to a note. + // Must be called before app.requestSingleInstanceLock(). + app.setAsDefaultProtocolClient("trilium"); + // Adds debug features like hotkeys for triggering dev tools and reload electronDebug(); electronDl({ saveAs: true }); @@ -63,13 +138,63 @@ async function main() { await serverInitializedPromise; console.log("Starting Electron..."); await onReady(); + + // Handle protocol URL passed when this is the *first* instance + // (Windows / Linux deliver the URL as a command-line argument). + // Only handle process.argv on non-macOS; macOS delivers protocol URLs via the open-url event. + if (process.platform !== "darwin") { + const protocolUrl = extractTriliumUrlFromArgs(process.argv); + if (protocolUrl) { + const noteId = parseTriliumUrl(protocolUrl); + if (noteId) { + const win = windowService.getLastFocusedWindow() ?? windowService.getMainWindow(); + if (win && !win.isDestroyed()) { + if (win.webContents.isLoading()) { + win.webContents.once("did-finish-load", () => navigateToNote(noteId)); + } else { + navigateToNote(noteId); + } + } + } + } + } }); app.on("will-quit", () => { globalShortcut.unregisterAll(); }); + // On macOS, protocol URLs for a *running* instance are delivered via + // the "open-url" event instead of "second-instance". + app.on("open-url", (event, url) => { + event.preventDefault(); + const noteId = parseTriliumUrl(url); + if (noteId) { + if (navigateToNote(noteId)) { + return; + } + // Window not ready yet — wait for it to be created and finish loading. + app.once("browser-window-created", (_event, win) => { + if (win.webContents.isLoading()) { + win.webContents.once("did-finish-load", () => navigateToNote(noteId)); + } else { + navigateToNote(noteId); + } + }); + } + }); + app.on("second-instance", (event, commandLine) => { + // Check if a trilium:// URL or --open-note flag was supplied. + const protocolUrl = extractTriliumUrlFromArgs(commandLine); + if (protocolUrl) { + const noteId = parseTriliumUrl(protocolUrl); + if (noteId) { + navigateToNote(noteId); + return; + } + } + const lastFocusedWindow = windowService.getLastFocusedWindow(); if (commandLine.includes("--new-window")) { windowService.createExtraWindow("");