-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Expand file tree
/
Copy pathprotocol-handler.ts
More file actions
130 lines (114 loc) · 3.49 KB
/
protocol-handler.ts
File metadata and controls
130 lines (114 loc) · 3.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import windowService from "@triliumnext/server/src/services/window.js";
const PROTOCOL = "trilium";
/** Note ID to navigate to once the main window finishes loading. */
let pendingNoteId: string | null = null;
/**
* Parses a `trilium://` URL and returns the note ID, or `null` if the URL
* cannot be parsed.
*
* Supported formats:
* trilium://note/<noteId> (canonical)
* trilium://<noteId> (shorthand)
*/
function parseTriliumUrl(rawUrl: string): string | null {
let parsed: URL;
try {
parsed = new URL(rawUrl);
} catch {
return null;
}
if (parsed.protocol !== `${PROTOCOL}:`) {
return null;
}
// trilium://note/<noteId> → hostname = "note", pathname = "/<noteId>"
if (parsed.hostname === "note") {
// Extract first path segment only, ignoring trailing slashes and extra segments
const pathSegments = parsed.pathname.split("/").filter(Boolean);
const noteId = pathSegments[0]?.trim();
return noteId || null;
}
// trilium://<noteId> → hostname = "<noteId>"
const noteId = parsed.hostname.trim();
return noteId || null;
}
/**
* Scans process arguments for a `trilium://` URL or `--open-note=<noteId>`
* flag and returns the target note ID.
*/
function extractNoteIdFromArgs(args: string[]): string | null {
for (const arg of args) {
if (arg.startsWith(`${PROTOCOL}://`)) {
const noteId = parseTriliumUrl(arg);
// Only return if we got a valid noteId; otherwise continue scanning
if (noteId) {
return noteId;
}
continue;
}
const match = arg.match(/^--open-note=(.+)$/);
if (match) {
return match[1].trim() || null;
}
}
return null;
}
/**
* Focuses the appropriate window and sends navigation IPC to the renderer.
* Waits for `did-finish-load` if the page is still loading (first launch).
*
* Returns `false` when no usable window exists yet.
*/
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();
if (win.webContents.isLoading()) {
win.webContents.once("did-finish-load", () => {
win.webContents.send("openInSameTab", noteId);
});
} else {
win.webContents.send("openInSameTab", noteId);
}
return true;
}
/**
* Handles an incoming protocol URL (from `open-url`, `second-instance`, or
* first-launch argv). If the main window is not yet available the note ID is
* queued for {@link processPendingNavigation}.
*/
function handleProtocolUrl(url: string): void {
const noteId = parseTriliumUrl(url);
if (!noteId) {
return;
}
if (!navigateToNote(noteId)) {
// Window not created yet — defer until after onReady().
pendingNoteId = noteId;
}
}
/**
* Navigates to the note that was requested before the window was ready, then
* clears the pending state. Safe to call when there is nothing pending.
*/
function processPendingNavigation(): void {
if (pendingNoteId) {
const noteId = pendingNoteId;
pendingNoteId = null;
navigateToNote(noteId);
}
}
export default {
PROTOCOL,
parseTriliumUrl,
extractNoteIdFromArgs,
navigateToNote,
handleProtocolUrl,
processPendingNavigation,
};