papyr-react 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +87 -0
  3. package/dist/blocks/WorkspaceBlock/WorkspaceBlock.d.ts +79 -0
  4. package/dist/blocks/index.d.ts +2 -0
  5. package/dist/components/Breadcrumbs/Breadcrumbs.d.ts +12 -0
  6. package/dist/components/Breadcrumbs/index.d.ts +2 -0
  7. package/dist/components/DoubleSidebarLayout/DoubleSidebarLayout.d.ts +49 -0
  8. package/dist/components/DoubleSidebarLayout/index.d.ts +2 -0
  9. package/dist/components/EmptyState/EmptyState.d.ts +31 -0
  10. package/dist/components/EmptyState/index.d.ts +2 -0
  11. package/dist/components/FileHierarchy/FileHierarchy.d.ts +19 -0
  12. package/dist/components/FileHierarchy/FileHierarchy.test.d.ts +1 -0
  13. package/dist/components/FileHierarchy/MemoizedFolderTree.d.ts +16 -0
  14. package/dist/components/FileHierarchy/index.d.ts +3 -0
  15. package/dist/components/FileHierarchy/types.d.ts +9 -0
  16. package/dist/components/FileHierarchy/utils.d.ts +5 -0
  17. package/dist/components/FileSearch/FileSearch.d.ts +27 -0
  18. package/dist/components/FileSearch/index.d.ts +2 -0
  19. package/dist/components/GraphView/GraphView.d.ts +19 -0
  20. package/dist/components/GraphView/index.d.ts +2 -0
  21. package/dist/components/HoverPreview/HoverPreview.d.ts +12 -0
  22. package/dist/components/HoverPreview/index.d.ts +2 -0
  23. package/dist/components/MiniGraph/MiniGraph.d.ts +8 -0
  24. package/dist/components/MiniGraph/MiniGraph.test.d.ts +1 -0
  25. package/dist/components/MiniGraph/index.d.ts +2 -0
  26. package/dist/components/NotePreview/NotePreviewContent.d.ts +14 -0
  27. package/dist/components/NotePreview/index.d.ts +2 -0
  28. package/dist/components/NoteViewer/NoteViewer.d.ts +15 -0
  29. package/dist/components/NoteViewer/NoteViewer.test.d.ts +1 -0
  30. package/dist/components/NoteViewer/index.d.ts +2 -0
  31. package/dist/components/SearchBar/SearchBar.d.ts +9 -0
  32. package/dist/components/SearchBar/index.d.ts +2 -0
  33. package/dist/components/SearchDropdown/SearchDropdown.d.ts +12 -0
  34. package/dist/components/SearchDropdown/index.d.ts +1 -0
  35. package/dist/components/SidebarLayout/SidebarLayout.d.ts +33 -0
  36. package/dist/components/SidebarLayout/index.d.ts +2 -0
  37. package/dist/components/TableOfContents/TableOfContents.d.ts +10 -0
  38. package/dist/components/TableOfContents/TableOfContents.test.d.ts +1 -0
  39. package/dist/components/TableOfContents/index.d.ts +1 -0
  40. package/dist/components/TagFilter/TagFilter.d.ts +9 -0
  41. package/dist/components/TagFilter/index.d.ts +2 -0
  42. package/dist/components/Toast/Toast.d.ts +11 -0
  43. package/dist/components/index.d.ts +16 -0
  44. package/dist/hooks/index.d.ts +3 -0
  45. package/dist/hooks/useActiveNote.d.ts +27 -0
  46. package/dist/hooks/useActiveNote.test.d.ts +1 -0
  47. package/dist/hooks/useNotes.d.ts +3 -0
  48. package/dist/hooks/useRoutableActiveNote.d.ts +16 -0
  49. package/dist/index.d.ts +6 -0
  50. package/dist/index.js +3178 -0
  51. package/dist/index.js.map +1 -0
  52. package/dist/providers/PapyrProvider.d.ts +24 -0
  53. package/dist/providers/index.d.ts +2 -0
  54. package/dist/style.css +1548 -0
  55. package/dist/styles/index.d.ts +0 -0
  56. package/dist/types/index.d.ts +1 -0
  57. package/dist/utils/graphUtils.d.ts +18 -0
  58. package/dist/utils/hydration.d.ts +8 -0
  59. package/dist/utils/index.d.ts +3 -0
  60. package/dist/utils/linkUtils.d.ts +10 -0
  61. package/package.json +87 -0
  62. package/src/styles/index.ts +2 -0
  63. package/src/styles/theme.css +184 -0
  64. package/style.css +1 -0
package/dist/index.js ADDED
@@ -0,0 +1,3178 @@
1
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
2
+ import { useState, useEffect, useMemo, useCallback, useRef, useLayoutEffect, createContext, useContext, memo } from 'react';
3
+ import { createPortal } from 'react-dom';
4
+ import clsx from 'clsx';
5
+ import { select } from 'd3-selection';
6
+ import { zoomIdentity, zoom } from 'd3-zoom';
7
+ import { drag } from 'd3-drag';
8
+ import { forceSimulation, forceLink, forceManyBody, forceCollide, forceCenter } from 'd3-force';
9
+ import { createRoot } from 'react-dom/client';
10
+ import { importSearchIndex, searchNotes } from 'papyr-core/runtime';
11
+ import { useFloating, autoUpdate, offset, flip, shift } from '@floating-ui/react';
12
+
13
+ const shell = "_shell_j3ea5_1";
14
+ const root = "_root_j3ea5_8";
15
+ const canvas$1 = "_canvas_j3ea5_16";
16
+ const fullscreenButton = "_fullscreenButton_j3ea5_27";
17
+ const fullscreenOverlay = "_fullscreenOverlay_j3ea5_49";
18
+ const fullscreenBackdrop = "_fullscreenBackdrop_j3ea5_58";
19
+ const fullscreenDialog = "_fullscreenDialog_j3ea5_65";
20
+ const fullscreenHeader = "_fullscreenHeader_j3ea5_77";
21
+ const fullscreenClose = "_fullscreenClose_j3ea5_86";
22
+ const fullscreenBody = "_fullscreenBody_j3ea5_94";
23
+ const fullscreenGraph = "_fullscreenGraph_j3ea5_99";
24
+ const emptyState$2 = "_emptyState_j3ea5_104";
25
+ const node$1 = "_node_j3ea5_115";
26
+ const isActive = "_isActive_j3ea5_120";
27
+ const label = "_label_j3ea5_124";
28
+ const styles$f = {
29
+ shell: shell,
30
+ root: root,
31
+ canvas: canvas$1,
32
+ fullscreenButton: fullscreenButton,
33
+ fullscreenOverlay: fullscreenOverlay,
34
+ fullscreenBackdrop: fullscreenBackdrop,
35
+ fullscreenDialog: fullscreenDialog,
36
+ fullscreenHeader: fullscreenHeader,
37
+ fullscreenClose: fullscreenClose,
38
+ fullscreenBody: fullscreenBody,
39
+ fullscreenGraph: fullscreenGraph,
40
+ emptyState: emptyState$2,
41
+ node: node$1,
42
+ isActive: isActive,
43
+ label: label
44
+ };
45
+
46
+ const TEXT_FALLBACK_CHAR_WIDTH = 8.5;
47
+ const TEXT_FALLBACK_HEIGHT = 15;
48
+ const LABEL_MIN_WIDTH = 40;
49
+ const LABEL_GAP_Y = 8;
50
+ const LABEL_EXTRA_PAD_X = 8;
51
+ const LABEL_EXTRA_PAD_Y = 6;
52
+ const TEXT_STROKE_PAD = 2;
53
+ const AUTO_ZOOM_PADDING = 48;
54
+ const DEFAULT_ZOOM_EXTENT = [0.35, 4];
55
+ const LABEL_FONT_BASE_PX = 11;
56
+ const LABEL_FONT_DISPLAY_SCALE = 1;
57
+ const LABEL_FONT_SCALE_MIN = 0.8;
58
+ const LABEL_FONT_SCALE_MAX = 1.5;
59
+ const NODE_RADIUS_MIN = 5;
60
+ const NODE_RADIUS_MAX = 14;
61
+ const NODE_RADIUS_POWER = 0.5;
62
+ const NODE_RADIUS_SCALE = 1;
63
+ const NODE_RADIUS_MIN_SCALED = NODE_RADIUS_MIN * NODE_RADIUS_SCALE;
64
+ const hasNodePosition = (node) => {
65
+ return typeof node.x === "number" && Number.isFinite(node.x) && typeof node.y === "number" && Number.isFinite(node.y);
66
+ };
67
+ function getNodeLabelSize(d, labelScale = 1) {
68
+ const text = d.node.label ?? d.node.id;
69
+ const width = (d.textWidth ?? Math.max(text.length * TEXT_FALLBACK_CHAR_WIDTH, LABEL_MIN_WIDTH)) + TEXT_STROKE_PAD;
70
+ const height = (d.textHeight ?? TEXT_FALLBACK_HEIGHT) + TEXT_STROKE_PAD;
71
+ return { width: width * labelScale, height: height * labelScale };
72
+ }
73
+ function getNodeVisualExtent(d, labelScale = 1) {
74
+ const r = d.radius;
75
+ const { width, height } = getNodeLabelSize(d, labelScale);
76
+ const halfWidth = Math.max(r, width / 2) + LABEL_EXTRA_PAD_X;
77
+ const top = r + LABEL_EXTRA_PAD_Y;
78
+ const bottom = r + LABEL_GAP_Y + height + LABEL_EXTRA_PAD_Y;
79
+ return { halfWidth, top, bottom };
80
+ }
81
+ const clamp = (value, min, max) => {
82
+ if (value < min) {
83
+ return min;
84
+ }
85
+ if (value > max) {
86
+ return max;
87
+ }
88
+ return value;
89
+ };
90
+ const computeGraphBounds = (nodes, labelScale = 1) => {
91
+ let minX = Infinity;
92
+ let maxX = -Infinity;
93
+ let minY = Infinity;
94
+ let maxY = -Infinity;
95
+ let hasAny = false;
96
+ for (const node of nodes) {
97
+ if (!hasNodePosition(node)) {
98
+ continue;
99
+ }
100
+ const { halfWidth, top, bottom } = getNodeVisualExtent(node, labelScale);
101
+ const nodeMinX = node.x - halfWidth;
102
+ const nodeMaxX = node.x + halfWidth;
103
+ const nodeMinY = node.y - top;
104
+ const nodeMaxY = node.y + bottom;
105
+ hasAny = true;
106
+ if (nodeMinX < minX) minX = nodeMinX;
107
+ if (nodeMaxX > maxX) maxX = nodeMaxX;
108
+ if (nodeMinY < minY) minY = nodeMinY;
109
+ if (nodeMaxY > maxY) maxY = nodeMaxY;
110
+ }
111
+ if (!hasAny) {
112
+ return null;
113
+ }
114
+ return { minX, maxX, minY, maxY };
115
+ };
116
+ const NODE_COLOR_FALLBACK = "var(--papyr-graph-node, rgba(255, 255, 255, 0.85))";
117
+ const NODE_OUTLINE = "var(--papyr-graph-stroke, rgba(0, 0, 0, 0.25))";
118
+ const LINK_COLOR = "var(--papyr-graph-link, rgba(255, 255, 255, 0.25))";
119
+ const LINK_HIGHLIGHT_COLOR = "var(--papyr-graph-link-active, rgba(255, 255, 255, 0.6))";
120
+ const LINK_STROKE_WIDTH = 1;
121
+ const LINK_HIGHLIGHT_STROKE_WIDTH = 1.5;
122
+ const BASE_LINK_OPACITY = 0.7;
123
+ const DIMMED_LINK_OPACITY = 0.08;
124
+ const FOCUS_LINK_OPACITY = 0.7;
125
+ const HIGHLIGHT_LINK_OPACITY = 1;
126
+ const DIMMED_NODE_OPACITY = 0.4;
127
+ function normalizeGraph(graph, filterNode) {
128
+ if (!graph) {
129
+ return {
130
+ nodes: [],
131
+ links: [],
132
+ adjacency: /* @__PURE__ */ new Map()
133
+ };
134
+ }
135
+ const nodes = [];
136
+ const adjacency = /* @__PURE__ */ new Map();
137
+ for (const [, node] of graph.nodes) {
138
+ if (filterNode && !filterNode(node)) {
139
+ continue;
140
+ }
141
+ nodes.push(node);
142
+ adjacency.set(node.id, /* @__PURE__ */ new Set());
143
+ }
144
+ if (nodes.length === 0) {
145
+ return { nodes, links: [], adjacency };
146
+ }
147
+ const allowed = new Set(nodes.map((node) => node.id));
148
+ const links = [];
149
+ for (const edge of graph.edges) {
150
+ const { source, target } = edge;
151
+ if (!allowed.has(source) || !allowed.has(target) || source === target) {
152
+ continue;
153
+ }
154
+ adjacency.get(source)?.add(target);
155
+ adjacency.get(target)?.add(source);
156
+ links.push({ source, target });
157
+ }
158
+ return { nodes, links, adjacency };
159
+ }
160
+ function getNeighborhood(graph, slug) {
161
+ const neighbors = /* @__PURE__ */ new Set([slug]);
162
+ graph.edges.forEach((edge) => {
163
+ if (edge.source === slug) {
164
+ neighbors.add(edge.target);
165
+ }
166
+ if (edge.target === slug) {
167
+ neighbors.add(edge.source);
168
+ }
169
+ });
170
+ return neighbors;
171
+ }
172
+ const resolveLinkEndpointId = (value) => {
173
+ if (typeof value === "string") {
174
+ return value;
175
+ }
176
+ if (typeof value === "number") {
177
+ return String(value);
178
+ }
179
+ return value.id;
180
+ };
181
+ const resolveLinkCoordinate = (value, axis) => {
182
+ if (typeof value === "object" && value !== null) {
183
+ return axis === "x" ? value.x ?? 0 : value.y ?? 0;
184
+ }
185
+ return 0;
186
+ };
187
+ const GraphCanvas = ({
188
+ graph,
189
+ className,
190
+ filterNode,
191
+ focusPredicate,
192
+ nodeColor,
193
+ zoomExtent,
194
+ focusNodeId = null,
195
+ onNodeSelect
196
+ }) => {
197
+ const containerRef = useRef(null);
198
+ const svgRef = useRef(null);
199
+ const zoomBehaviorRef = useRef(null);
200
+ const transformRef = useRef(zoomIdentity);
201
+ const simulationRef = useRef(null);
202
+ const nodeLookupRef = useRef(/* @__PURE__ */ new Map());
203
+ const hoveredNodeRef = useRef(null);
204
+ const pendingFocusRef = useRef(null);
205
+ const draggingNodeRef = useRef(null);
206
+ const labelLayerRef = useRef(null);
207
+ const autoZoomPendingRef = useRef(false);
208
+ const autoZoomFrameRef = useRef(null);
209
+ const autoZoomReadyRef = useRef(false);
210
+ const labelFontScaleRef = useRef(LABEL_FONT_DISPLAY_SCALE);
211
+ const [activeNode, setActiveNode] = useState(null);
212
+ const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
213
+ useLayoutEffect(() => {
214
+ const element = containerRef.current;
215
+ if (!element) {
216
+ return;
217
+ }
218
+ const observer = new ResizeObserver((entries) => {
219
+ const entry = entries[0];
220
+ if (!entry) {
221
+ return;
222
+ }
223
+ const { width, height } = entry.contentRect;
224
+ setDimensions({
225
+ width: Math.max(200, Math.floor(width)),
226
+ height: Math.max(240, Math.floor(height))
227
+ });
228
+ });
229
+ observer.observe(element);
230
+ return () => observer.disconnect();
231
+ }, []);
232
+ const normalized = useMemo(() => normalizeGraph(graph, filterNode), [graph, filterNode]);
233
+ const hasGraph = normalized.nodes.length > 0;
234
+ const resolvedZoomExtent = zoomExtent ?? DEFAULT_ZOOM_EXTENT;
235
+ useEffect(() => {
236
+ setActiveNode(focusNodeId ?? null);
237
+ }, [focusNodeId]);
238
+ const focusMatches = useMemo(() => {
239
+ if (!focusPredicate) {
240
+ return null;
241
+ }
242
+ const matches = /* @__PURE__ */ new Set();
243
+ for (const node of normalized.nodes) {
244
+ if (focusPredicate(node)) {
245
+ matches.add(node.id);
246
+ }
247
+ }
248
+ return matches;
249
+ }, [focusPredicate, normalized.nodes]);
250
+ const resolvedNodeColor = useCallback(
251
+ (node) => {
252
+ if (nodeColor) {
253
+ return nodeColor(node);
254
+ }
255
+ return NODE_COLOR_FALLBACK;
256
+ },
257
+ [nodeColor]
258
+ );
259
+ const { nodeWeights, maxWeight } = useMemo(() => {
260
+ const weights = /* @__PURE__ */ new Map();
261
+ let currentMax = 0;
262
+ for (const node of normalized.nodes) {
263
+ const metadataWeight = typeof node.metadata?.weight === "number" ? node.metadata.weight : null;
264
+ const degree = normalized.adjacency.get(node.id)?.size ?? 0;
265
+ const weight = metadataWeight ?? degree;
266
+ weights.set(node.id, weight);
267
+ if (weight > currentMax) {
268
+ currentMax = weight;
269
+ }
270
+ }
271
+ return { nodeWeights: weights, maxWeight: currentMax };
272
+ }, [normalized]);
273
+ const resolveNodeRadius = useCallback(
274
+ (weight) => {
275
+ if (!maxWeight || !Number.isFinite(maxWeight) || maxWeight <= 0) {
276
+ return NODE_RADIUS_MIN_SCALED;
277
+ }
278
+ if (!Number.isFinite(weight) || weight <= 0) {
279
+ return NODE_RADIUS_MIN_SCALED;
280
+ }
281
+ const normalizedWeight = clamp(weight / maxWeight, 0, 1);
282
+ const eased = Math.pow(normalizedWeight, NODE_RADIUS_POWER);
283
+ const base = NODE_RADIUS_MIN + eased * (NODE_RADIUS_MAX - NODE_RADIUS_MIN);
284
+ return base * NODE_RADIUS_SCALE;
285
+ },
286
+ [maxWeight]
287
+ );
288
+ const nodeData = useMemo(() => {
289
+ return normalized.nodes.map((node) => {
290
+ const weight = nodeWeights.get(node.id) ?? 0;
291
+ return {
292
+ id: node.id,
293
+ node,
294
+ weight,
295
+ radius: resolveNodeRadius(weight),
296
+ color: resolvedNodeColor(node)
297
+ };
298
+ });
299
+ }, [normalized.nodes, nodeWeights, resolveNodeRadius, resolvedNodeColor]);
300
+ const linkData = useMemo(() => {
301
+ return normalized.links.map((link, index) => ({
302
+ source: link.source,
303
+ target: link.target,
304
+ uid: `${link.source}::${link.target}::${index}`
305
+ }));
306
+ }, [normalized.links]);
307
+ const handleDefaultNavigation = useCallback((node) => {
308
+ if (typeof window === "undefined") {
309
+ return;
310
+ }
311
+ const permalink = typeof node.metadata?.permalink === "string" ? node.metadata.permalink : null;
312
+ const fallback = `#/note/${node.id}`;
313
+ const target = permalink && permalink.length > 0 ? permalink : fallback;
314
+ window.open(target, "_self");
315
+ }, []);
316
+ const focusOnNode = useCallback(
317
+ (datum) => {
318
+ if (!svgRef.current || !zoomBehaviorRef.current) {
319
+ return false;
320
+ }
321
+ if (!hasNodePosition(datum)) {
322
+ return false;
323
+ }
324
+ if (!dimensions.width || !dimensions.height) {
325
+ return false;
326
+ }
327
+ const currentScale = transformRef.current?.k ?? 1;
328
+ const tx = dimensions.width / 2 - currentScale * datum.x;
329
+ const ty = dimensions.height / 2 - currentScale * datum.y;
330
+ const nextTransform = zoomIdentity.translate(tx, ty).scale(currentScale);
331
+ transformRef.current = nextTransform;
332
+ select(svgRef.current).call(zoomBehaviorRef.current.transform, nextTransform);
333
+ return true;
334
+ },
335
+ [dimensions.height, dimensions.width]
336
+ );
337
+ const cancelAutoZoomFrame = useCallback(() => {
338
+ if (typeof window === "undefined") {
339
+ return;
340
+ }
341
+ if (autoZoomFrameRef.current !== null) {
342
+ window.cancelAnimationFrame(autoZoomFrameRef.current);
343
+ autoZoomFrameRef.current = null;
344
+ }
345
+ }, []);
346
+ const applyLabelFontSize = useCallback(
347
+ (zoomScale) => {
348
+ if (!labelLayerRef.current) {
349
+ return;
350
+ }
351
+ const safeScale = clamp(zoomScale, 0.15, 6);
352
+ const labelScale = clamp(
353
+ LABEL_FONT_DISPLAY_SCALE / safeScale,
354
+ LABEL_FONT_SCALE_MIN,
355
+ LABEL_FONT_SCALE_MAX
356
+ );
357
+ labelFontScaleRef.current = labelScale;
358
+ const fontSizePx = LABEL_FONT_BASE_PX * labelScale;
359
+ select(labelLayerRef.current).selectAll("text[data-label]").style("font-size", `${fontSizePx}px`);
360
+ },
361
+ []
362
+ );
363
+ const applyAutoZoom = useCallback(() => {
364
+ if (!svgRef.current || !zoomBehaviorRef.current) {
365
+ return false;
366
+ }
367
+ if (!dimensions.width || !dimensions.height) {
368
+ return false;
369
+ }
370
+ const bounds = computeGraphBounds(nodeLookupRef.current.values(), labelFontScaleRef.current);
371
+ if (!bounds) {
372
+ return false;
373
+ }
374
+ const boundsWidth = bounds.maxX - bounds.minX;
375
+ const boundsHeight = bounds.maxY - bounds.minY;
376
+ if (!Number.isFinite(boundsWidth) || !Number.isFinite(boundsHeight)) {
377
+ return false;
378
+ }
379
+ const innerWidth = Math.max(1, dimensions.width - AUTO_ZOOM_PADDING * 2);
380
+ const innerHeight = Math.max(1, dimensions.height - AUTO_ZOOM_PADDING * 2);
381
+ const rawScale = Math.min(
382
+ 1,
383
+ innerWidth / Math.max(boundsWidth, 1),
384
+ innerHeight / Math.max(boundsHeight, 1)
385
+ );
386
+ if (!Number.isFinite(rawScale) || rawScale <= 0) {
387
+ return false;
388
+ }
389
+ const [providedMin, providedMax] = resolvedZoomExtent;
390
+ const minScale = Math.min(providedMin, rawScale);
391
+ const maxScale = providedMax;
392
+ const scale = clamp(rawScale, minScale, maxScale);
393
+ let targetX = (bounds.minX + bounds.maxX) / 2;
394
+ let targetY = (bounds.minY + bounds.maxY) / 2;
395
+ const focusId = pendingFocusRef.current ?? activeNode;
396
+ if (focusId) {
397
+ const focusNode = nodeLookupRef.current.get(focusId);
398
+ if (focusNode && hasNodePosition(focusNode)) {
399
+ targetX = focusNode.x;
400
+ targetY = focusNode.y;
401
+ }
402
+ }
403
+ const tx = dimensions.width / 2 - scale * targetX;
404
+ const ty = dimensions.height / 2 - scale * targetY;
405
+ const nextTransform = zoomIdentity.translate(tx, ty).scale(scale);
406
+ transformRef.current = nextTransform;
407
+ const behavior = zoomBehaviorRef.current;
408
+ behavior.scaleExtent([minScale, maxScale]);
409
+ select(svgRef.current).call(behavior.transform, nextTransform);
410
+ return true;
411
+ }, [activeNode, dimensions.height, dimensions.width, resolvedZoomExtent]);
412
+ const scheduleAutoZoom = useCallback(() => {
413
+ if (typeof window === "undefined") {
414
+ return;
415
+ }
416
+ autoZoomPendingRef.current = true;
417
+ if (autoZoomFrameRef.current !== null) {
418
+ return;
419
+ }
420
+ const tick = () => {
421
+ autoZoomFrameRef.current = null;
422
+ if (!autoZoomPendingRef.current) {
423
+ return;
424
+ }
425
+ const applied = applyAutoZoom();
426
+ if (applied) {
427
+ autoZoomPendingRef.current = false;
428
+ return;
429
+ }
430
+ autoZoomFrameRef.current = window.requestAnimationFrame(tick);
431
+ };
432
+ autoZoomFrameRef.current = window.requestAnimationFrame(tick);
433
+ }, [applyAutoZoom]);
434
+ const flushPendingFocus = useCallback(() => {
435
+ const targetId = pendingFocusRef.current;
436
+ if (!targetId) {
437
+ return;
438
+ }
439
+ const node = nodeLookupRef.current.get(targetId);
440
+ if (!node) {
441
+ return;
442
+ }
443
+ const didFocus = focusOnNode(node);
444
+ if (didFocus) {
445
+ pendingFocusRef.current = null;
446
+ }
447
+ }, [focusOnNode]);
448
+ useEffect(() => {
449
+ return () => {
450
+ autoZoomPendingRef.current = false;
451
+ cancelAutoZoomFrame();
452
+ };
453
+ }, [cancelAutoZoomFrame]);
454
+ useEffect(() => {
455
+ if (!hasGraph) {
456
+ autoZoomPendingRef.current = false;
457
+ cancelAutoZoomFrame();
458
+ return;
459
+ }
460
+ if (!autoZoomReadyRef.current) {
461
+ return;
462
+ }
463
+ scheduleAutoZoom();
464
+ }, [
465
+ hasGraph,
466
+ scheduleAutoZoom,
467
+ cancelAutoZoomFrame,
468
+ dimensions.height,
469
+ dimensions.width,
470
+ resolvedZoomExtent
471
+ ]);
472
+ const requestFocusNode = useCallback(
473
+ (nodeId) => {
474
+ pendingFocusRef.current = nodeId;
475
+ if (!nodeId) {
476
+ return;
477
+ }
478
+ flushPendingFocus();
479
+ },
480
+ [flushPendingFocus]
481
+ );
482
+ const handleNodeSelection = useCallback(
483
+ (datum) => {
484
+ setActiveNode(datum.id);
485
+ const didFocus = focusOnNode(datum);
486
+ pendingFocusRef.current = didFocus ? null : datum.id;
487
+ if (onNodeSelect) {
488
+ onNodeSelect(datum.node);
489
+ } else {
490
+ handleDefaultNavigation(datum.node);
491
+ }
492
+ },
493
+ [focusOnNode, handleDefaultNavigation, onNodeSelect]
494
+ );
495
+ useEffect(() => {
496
+ requestFocusNode(focusNodeId ?? null);
497
+ if (simulationRef.current && focusNodeId) {
498
+ const sim = simulationRef.current;
499
+ if (sim.alpha() < 0.1) {
500
+ sim.alpha(0.3).restart();
501
+ }
502
+ }
503
+ }, [focusNodeId, requestFocusNode]);
504
+ const applyHighlight = useCallback(
505
+ (hoveredId) => {
506
+ if (!svgRef.current) {
507
+ return;
508
+ }
509
+ const svg = select(svgRef.current);
510
+ const nodesSelection = svg.selectAll("circle[data-node]");
511
+ const linksSelection = svg.selectAll("line[data-link]");
512
+ const labelsSelection = svg.selectAll("text[data-label]");
513
+ const highlightId = hoveredId;
514
+ const hoveredSet = highlightId ? /* @__PURE__ */ new Set([
515
+ highlightId,
516
+ ...Array.from(normalized.adjacency.get(highlightId) ?? [])
517
+ ]) : null;
518
+ const focusSet = focusMatches && focusMatches.size > 0 ? focusMatches : null;
519
+ nodesSelection.classed(styles$f.isActive, (d) => d.id === activeNode).style("opacity", (d) => {
520
+ if (hoveredSet) {
521
+ return hoveredSet.has(d.id) ? 1 : DIMMED_NODE_OPACITY;
522
+ }
523
+ if (focusSet) {
524
+ return focusSet.has(d.id) ? 1 : DIMMED_NODE_OPACITY;
525
+ }
526
+ return 1;
527
+ });
528
+ linksSelection.style("opacity", (d) => {
529
+ const sourceId = resolveLinkEndpointId(d.source);
530
+ const targetId = resolveLinkEndpointId(d.target);
531
+ if (highlightId) {
532
+ return sourceId === highlightId || targetId === highlightId ? HIGHLIGHT_LINK_OPACITY : DIMMED_LINK_OPACITY;
533
+ }
534
+ if (focusSet) {
535
+ return focusSet.has(sourceId) && focusSet.has(targetId) ? FOCUS_LINK_OPACITY : DIMMED_LINK_OPACITY;
536
+ }
537
+ return BASE_LINK_OPACITY;
538
+ }).attr("stroke-width", hoveredId ? LINK_HIGHLIGHT_STROKE_WIDTH : LINK_STROKE_WIDTH).attr("stroke", (d) => {
539
+ const sourceId = resolveLinkEndpointId(d.source);
540
+ const targetId = resolveLinkEndpointId(d.target);
541
+ if (highlightId && (sourceId === highlightId || targetId === highlightId)) {
542
+ return LINK_HIGHLIGHT_COLOR;
543
+ }
544
+ return LINK_COLOR;
545
+ });
546
+ labelsSelection.style("opacity", (d) => {
547
+ if (hoveredSet) {
548
+ return hoveredSet.has(d.id) ? 1 : 0.4;
549
+ }
550
+ if (focusSet && focusSet.has(d.id)) {
551
+ return 1;
552
+ }
553
+ return 0.7;
554
+ });
555
+ },
556
+ [activeNode, focusMatches, normalized.adjacency]
557
+ );
558
+ useEffect(() => {
559
+ if (!svgRef.current) {
560
+ return;
561
+ }
562
+ const svg = select(svgRef.current);
563
+ const viewport = svg.select("g[data-viewport]");
564
+ const linkLayer = viewport.select("g[data-links]");
565
+ const nodeLayer = viewport.select("g[data-nodes]");
566
+ const labelLayer = viewport.select("g[data-labels]");
567
+ const nodes = nodeData.map((datum) => ({ ...datum }));
568
+ const links = linkData.map((link) => ({ ...link }));
569
+ nodeLookupRef.current = new Map(nodes.map((node) => [node.id, node]));
570
+ autoZoomReadyRef.current = false;
571
+ autoZoomPendingRef.current = false;
572
+ cancelAutoZoomFrame();
573
+ pendingFocusRef.current = focusNodeId ?? null;
574
+ if (focusNodeId) {
575
+ const focusNode = nodes.find((n) => n.id === focusNodeId);
576
+ if (focusNode) {
577
+ focusNode.fx = 0;
578
+ focusNode.fy = 0;
579
+ focusNode.x = 0;
580
+ focusNode.y = 0;
581
+ }
582
+ }
583
+ const linkSelection = linkLayer.selectAll("line[data-link]").data(links, (d) => d.uid);
584
+ linkSelection.exit().remove();
585
+ const linkEnter = linkSelection.enter().append("line").attr("data-link", "true").attr("stroke-width", LINK_STROKE_WIDTH).attr("stroke", LINK_COLOR).attr("stroke-linecap", "round");
586
+ const mergedLinks = linkEnter.merge(linkSelection);
587
+ labelLayerRef.current = labelLayer.node();
588
+ const labelSelection = labelLayer.selectAll("text[data-label]").data(nodes, (d) => d.id);
589
+ labelSelection.exit().remove();
590
+ const labelEnter = labelSelection.enter().append("text").attr("data-label", "true").attr("class", styles$f.label).attr("text-anchor", "middle").attr("alignment-baseline", "hanging").attr("dy", (d) => `${d.radius + 8}px`).style("opacity", 1).text((d) => d.node.label ?? d.node.id);
591
+ const mergedLabels = labelEnter.merge(labelSelection);
592
+ mergedLabels.style(
593
+ "font-size",
594
+ `${LABEL_FONT_BASE_PX * labelFontScaleRef.current}px`
595
+ );
596
+ requestAnimationFrame(() => {
597
+ mergedLabels.each(function(d) {
598
+ const textElement = this;
599
+ try {
600
+ const currentX = textElement.getAttribute("x");
601
+ const currentY = textElement.getAttribute("y");
602
+ if (!currentX || !currentY || currentX === "0" || currentY === "0") {
603
+ textElement.setAttribute("x", "0");
604
+ textElement.setAttribute("y", "0");
605
+ }
606
+ const bbox = textElement.getBBox();
607
+ const baseScale = labelFontScaleRef.current || 1;
608
+ d.textWidth = bbox.width / baseScale;
609
+ d.textHeight = bbox.height / baseScale;
610
+ } catch {
611
+ const text = d.node.label ?? d.node.id;
612
+ d.textWidth = text.length * TEXT_FALLBACK_CHAR_WIDTH;
613
+ d.textHeight = TEXT_FALLBACK_HEIGHT;
614
+ }
615
+ });
616
+ });
617
+ const nodeSelection = nodeLayer.selectAll("circle[data-node]").data(nodes, (d) => d.id);
618
+ nodeSelection.exit().remove();
619
+ const nodeEnter = nodeSelection.enter().append("circle").attr("data-node", "true").attr("class", styles$f.node).attr("fill", (d) => d.color).attr("stroke", NODE_OUTLINE).attr("stroke-width", 1).attr("r", (d) => d.radius).on("mouseenter", (_event, datum) => {
620
+ hoveredNodeRef.current = datum.id;
621
+ applyHighlight(datum.id);
622
+ }).on("mouseleave", (_event, datum) => {
623
+ if (draggingNodeRef.current === datum.id) {
624
+ return;
625
+ }
626
+ hoveredNodeRef.current = null;
627
+ applyHighlight(null);
628
+ }).on("click", (event, datum) => {
629
+ event.stopPropagation();
630
+ handleNodeSelection(datum);
631
+ });
632
+ nodeEnter.append("title").text((d) => d.node.label ?? d.node.id);
633
+ const mergedNodes = nodeEnter.merge(nodeSelection);
634
+ const centerX = focusNodeId ? 0 : dimensions.width / 2;
635
+ const centerY = focusNodeId ? 0 : dimensions.height / 2 - 20;
636
+ const simulation = forceSimulation(nodes).force(
637
+ "link",
638
+ forceLink(links).id((d) => d.id).distance(() => 135).strength(0.18)
639
+ ).force("charge", forceManyBody().strength(-320)).force(
640
+ "collision",
641
+ forceCollide().radius((d) => {
642
+ const { halfWidth, top, bottom } = getNodeVisualExtent(d, labelFontScaleRef.current);
643
+ return Math.max(halfWidth, top, bottom);
644
+ })
645
+ ).force("center", forceCenter(centerX, centerY)).alphaDecay(0.07).alpha(0.9);
646
+ let tickCount = 0;
647
+ simulation.on("tick", () => {
648
+ tickCount += 1;
649
+ mergedLinks.attr("x1", (d) => resolveLinkCoordinate(d.source, "x")).attr("y1", (d) => resolveLinkCoordinate(d.source, "y")).attr("x2", (d) => resolveLinkCoordinate(d.target, "x")).attr("y2", (d) => resolveLinkCoordinate(d.target, "y"));
650
+ mergedNodes.attr("cx", (d) => d.x ?? 0).attr("cy", (d) => d.y ?? 0);
651
+ mergedLabels.attr("x", (d) => d.x ?? 0).attr("y", (d) => {
652
+ const y = d.y ?? 0;
653
+ const radius = d.radius;
654
+ return y + radius + LABEL_GAP_Y;
655
+ }).each(function(d) {
656
+ if (d.textWidth === void 0 || d.textHeight === void 0) {
657
+ const textElement = this;
658
+ try {
659
+ const bbox = textElement.getBBox();
660
+ d.textWidth = bbox.width;
661
+ d.textHeight = bbox.height;
662
+ } catch {
663
+ const text = d.node.label ?? d.node.id;
664
+ d.textWidth = Math.max(
665
+ text.length * TEXT_FALLBACK_CHAR_WIDTH,
666
+ LABEL_MIN_WIDTH
667
+ );
668
+ d.textHeight = TEXT_FALLBACK_HEIGHT;
669
+ }
670
+ }
671
+ });
672
+ const alpha = simulation.alpha();
673
+ if (pendingFocusRef.current && tickCount >= 10 && alpha < 0.3) {
674
+ flushPendingFocus();
675
+ }
676
+ if (alpha < 0.02) {
677
+ simulation.stop();
678
+ if (pendingFocusRef.current) {
679
+ flushPendingFocus();
680
+ setTimeout(() => {
681
+ if (pendingFocusRef.current) {
682
+ flushPendingFocus();
683
+ }
684
+ }, 50);
685
+ setTimeout(() => {
686
+ if (pendingFocusRef.current) {
687
+ flushPendingFocus();
688
+ }
689
+ }, 200);
690
+ }
691
+ }
692
+ });
693
+ simulationRef.current = simulation;
694
+ const dragBehavior = drag().on("start", (event) => {
695
+ if (!event.active) {
696
+ simulation.alphaTarget(0.3).restart();
697
+ }
698
+ draggingNodeRef.current = event.subject.id;
699
+ hoveredNodeRef.current = event.subject.id;
700
+ applyHighlight(event.subject.id);
701
+ event.subject.fx = event.subject.x;
702
+ event.subject.fy = event.subject.y;
703
+ }).on("drag", (event) => {
704
+ event.subject.fx = event.x;
705
+ event.subject.fy = event.y;
706
+ hoveredNodeRef.current = event.subject.id;
707
+ applyHighlight(event.subject.id);
708
+ }).on("end", (event) => {
709
+ if (!event.active) {
710
+ simulation.alphaTarget(0);
711
+ }
712
+ draggingNodeRef.current = null;
713
+ event.subject.fx = null;
714
+ event.subject.fy = null;
715
+ hoveredNodeRef.current = null;
716
+ applyHighlight(null);
717
+ });
718
+ mergedNodes.call(dragBehavior);
719
+ applyLabelFontSize(transformRef.current?.k ?? 1);
720
+ applyHighlight(hoveredNodeRef.current);
721
+ flushPendingFocus();
722
+ if (nodes.length === 0) {
723
+ autoZoomReadyRef.current = false;
724
+ autoZoomPendingRef.current = false;
725
+ cancelAutoZoomFrame();
726
+ } else {
727
+ const warmupIterations = Math.min(80, 16 + nodes.length * 2);
728
+ for (let i = 0; i < warmupIterations; i += 1) {
729
+ simulation.tick();
730
+ }
731
+ autoZoomReadyRef.current = true;
732
+ scheduleAutoZoom();
733
+ simulation.alpha(0.4).restart();
734
+ }
735
+ return () => {
736
+ simulation.stop();
737
+ };
738
+ }, [
739
+ dimensions.height,
740
+ dimensions.width,
741
+ handleNodeSelection,
742
+ linkData,
743
+ nodeData,
744
+ applyHighlight,
745
+ applyLabelFontSize,
746
+ flushPendingFocus,
747
+ focusNodeId,
748
+ cancelAutoZoomFrame,
749
+ scheduleAutoZoom
750
+ ]);
751
+ useEffect(() => {
752
+ if (!svgRef.current) {
753
+ return;
754
+ }
755
+ const svg = select(svgRef.current);
756
+ svg.attr("width", dimensions.width || 600);
757
+ svg.attr("height", dimensions.height || 400);
758
+ if (simulationRef.current) {
759
+ const centerX = focusNodeId ? 0 : dimensions.width / 2;
760
+ const centerY = focusNodeId ? 0 : dimensions.height / 2 - 20;
761
+ simulationRef.current.force("center", forceCenter(centerX, centerY));
762
+ simulationRef.current.alpha(0.4).restart();
763
+ }
764
+ }, [dimensions.height, dimensions.width, focusNodeId]);
765
+ useEffect(() => {
766
+ if (!svgRef.current) {
767
+ return;
768
+ }
769
+ const svg = select(svgRef.current);
770
+ const viewport = svg.select("g[data-viewport]");
771
+ const behavior = zoom().scaleExtent(resolvedZoomExtent).on("zoom", (event) => {
772
+ transformRef.current = event.transform;
773
+ viewport.attr("transform", event.transform.toString());
774
+ applyLabelFontSize(event.transform.k);
775
+ });
776
+ zoomBehaviorRef.current = behavior;
777
+ svg.call(behavior);
778
+ return () => {
779
+ svg.on(".zoom", null);
780
+ };
781
+ }, [applyLabelFontSize, resolvedZoomExtent]);
782
+ useEffect(() => {
783
+ applyHighlight(hoveredNodeRef.current);
784
+ }, [applyHighlight]);
785
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: clsx(styles$f.root, className), "data-papyr-component": "GraphView", children: [
786
+ /* @__PURE__ */ jsx("svg", { ref: svgRef, className: styles$f.canvas, role: "img", "aria-label": "Publish graph", children: /* @__PURE__ */ jsxs("g", { "data-viewport": true, children: [
787
+ /* @__PURE__ */ jsx("g", { "data-links": true }),
788
+ /* @__PURE__ */ jsx("g", { "data-nodes": true }),
789
+ /* @__PURE__ */ jsx("g", { "data-labels": true })
790
+ ] }) }),
791
+ !hasGraph && /* @__PURE__ */ jsx("div", { className: styles$f.emptyState, children: "No published links to explore yet." })
792
+ ] });
793
+ };
794
+ const isBrowser = typeof document !== "undefined";
795
+ const GraphView = ({
796
+ graph,
797
+ className,
798
+ activeSlug = null,
799
+ filterNode,
800
+ focusPredicate,
801
+ nodeColor,
802
+ zoomExtent,
803
+ onNodeSelect,
804
+ showFullscreenToggle = true,
805
+ fullscreenTitle = "Full site graph"
806
+ }) => {
807
+ const [isFullscreen, setIsFullscreen] = useState(false);
808
+ useEffect(() => {
809
+ if (!isFullscreen || !isBrowser) {
810
+ return;
811
+ }
812
+ const previous = document.body.style.overflow;
813
+ document.body.style.overflow = "hidden";
814
+ return () => {
815
+ document.body.style.overflow = previous;
816
+ };
817
+ }, [isFullscreen]);
818
+ const neighborhood = useMemo(() => {
819
+ if (!graph || !activeSlug) {
820
+ return null;
821
+ }
822
+ return getNeighborhood(graph, activeSlug);
823
+ }, [graph, activeSlug]);
824
+ const derivedFilter = useMemo(() => {
825
+ if (isFullscreen || !activeSlug || !neighborhood) {
826
+ return filterNode;
827
+ }
828
+ if (!filterNode) {
829
+ return (node) => neighborhood.has(node.id);
830
+ }
831
+ return (node) => filterNode(node) && neighborhood.has(node.id);
832
+ }, [filterNode, neighborhood, activeSlug, isFullscreen]);
833
+ const handleCloseFullscreen = useCallback(() => {
834
+ setIsFullscreen(false);
835
+ }, []);
836
+ const canToggleFullScreen = showFullscreenToggle && !!graph;
837
+ const mainGraph = /* @__PURE__ */ jsx(
838
+ GraphCanvas,
839
+ {
840
+ graph,
841
+ className,
842
+ filterNode: derivedFilter,
843
+ focusPredicate,
844
+ nodeColor,
845
+ zoomExtent,
846
+ focusNodeId: activeSlug ?? null,
847
+ onNodeSelect
848
+ }
849
+ );
850
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
851
+ /* @__PURE__ */ jsxs("div", { className: clsx(styles$f.shell, className), children: [
852
+ canToggleFullScreen && /* @__PURE__ */ jsx(
853
+ "button",
854
+ {
855
+ type: "button",
856
+ className: styles$f.fullscreenButton,
857
+ "aria-label": "Open full graph",
858
+ onClick: () => setIsFullscreen(true),
859
+ children: "⤢"
860
+ }
861
+ ),
862
+ mainGraph
863
+ ] }),
864
+ isFullscreen && isBrowser ? createPortal(
865
+ /* @__PURE__ */ jsxs("div", { className: styles$f.fullscreenOverlay, role: "dialog", "aria-modal": "true", children: [
866
+ /* @__PURE__ */ jsx("div", { className: styles$f.fullscreenBackdrop, onClick: handleCloseFullscreen }),
867
+ /* @__PURE__ */ jsxs("div", { className: styles$f.fullscreenDialog, children: [
868
+ /* @__PURE__ */ jsxs("header", { className: styles$f.fullscreenHeader, children: [
869
+ /* @__PURE__ */ jsx("span", { children: fullscreenTitle }),
870
+ /* @__PURE__ */ jsx(
871
+ "button",
872
+ {
873
+ type: "button",
874
+ className: styles$f.fullscreenClose,
875
+ onClick: handleCloseFullscreen,
876
+ children: "Close"
877
+ }
878
+ )
879
+ ] }),
880
+ /* @__PURE__ */ jsx("div", { className: styles$f.fullscreenBody, children: /* @__PURE__ */ jsx(
881
+ GraphCanvas,
882
+ {
883
+ graph,
884
+ className: clsx(className, styles$f.fullscreenGraph),
885
+ filterNode,
886
+ focusPredicate,
887
+ nodeColor,
888
+ focusNodeId: activeSlug ?? null,
889
+ zoomExtent,
890
+ onNodeSelect
891
+ }
892
+ ) })
893
+ ] })
894
+ ] }),
895
+ document.body
896
+ ) : null
897
+ ] });
898
+ };
899
+
900
+ const frame = "_frame_187pj_1";
901
+ const header$2 = "_header_187pj_12";
902
+ const title$3 = "_title_187pj_19";
903
+ const subtitle = "_subtitle_187pj_27";
904
+ const badge = "_badge_187pj_34";
905
+ const canvas = "_canvas_187pj_48";
906
+ const glow = "_glow_187pj_56";
907
+ const edge = "_edge_187pj_60";
908
+ const node = "_node_187pj_65";
909
+ const emptyState$1 = "_emptyState_187pj_71";
910
+ const styles$e = {
911
+ frame: frame,
912
+ header: header$2,
913
+ title: title$3,
914
+ subtitle: subtitle,
915
+ badge: badge,
916
+ canvas: canvas,
917
+ glow: glow,
918
+ edge: edge,
919
+ node: node,
920
+ emptyState: emptyState$1
921
+ };
922
+
923
+ const MiniGraph = ({ graph, className }) => {
924
+ const { nodes, edges, totalNodes, totalEdges } = useMemo(() => {
925
+ const MAX_NODES = 24;
926
+ const VIEWBOX_SIZE = 200;
927
+ const CENTER = VIEWBOX_SIZE / 2;
928
+ const graphNodes = Array.from(graph.nodes.values());
929
+ const sortedNodes = graphNodes.slice().sort((a, b) => (b.linkCount ?? 0) - (a.linkCount ?? 0)).slice(0, MAX_NODES);
930
+ const nodeIds = new Set(sortedNodes.map((node) => node.id));
931
+ const filteredEdges = graph.edges.filter(
932
+ (edge) => nodeIds.has(edge.source) && nodeIds.has(edge.target)
933
+ );
934
+ const maxLinks = Math.max(
935
+ 1,
936
+ ...sortedNodes.map((node) => node.linkCount ?? 0)
937
+ );
938
+ const orbitRadius = sortedNodes.length > 12 ? 70 : 62;
939
+ const positionedNodes = sortedNodes.map((node, index) => {
940
+ const angle = Math.PI * 2 * index / sortedNodes.length - Math.PI / 2;
941
+ const radius = 3 + (node.linkCount ?? 0) / maxLinks * 5;
942
+ return {
943
+ id: node.id,
944
+ label: node.label ?? node.id,
945
+ x: CENTER + orbitRadius * Math.cos(angle),
946
+ y: CENTER + orbitRadius * Math.sin(angle),
947
+ radius
948
+ };
949
+ });
950
+ const nodeLookup = new Map(positionedNodes.map((node) => [node.id, node]));
951
+ const positionedEdges = filteredEdges.map((edge) => ({
952
+ source: nodeLookup.get(edge.source) ?? null,
953
+ target: nodeLookup.get(edge.target) ?? null
954
+ })).filter((edge) => edge.source && edge.target).map((edge) => ({
955
+ source: edge.source,
956
+ target: edge.target
957
+ }));
958
+ return {
959
+ nodes: positionedNodes,
960
+ edges: positionedEdges,
961
+ totalNodes: graphNodes.length,
962
+ totalEdges: graph.edges.length
963
+ };
964
+ }, [graph]);
965
+ return /* @__PURE__ */ jsxs("div", { className: clsx(styles$e.frame, className), "data-papyr-component": "MiniGraph", children: [
966
+ /* @__PURE__ */ jsxs("div", { className: styles$e.header, children: [
967
+ /* @__PURE__ */ jsxs("div", { children: [
968
+ /* @__PURE__ */ jsx("div", { className: styles$e.title, children: "Mini graph" }),
969
+ /* @__PURE__ */ jsxs("div", { className: styles$e.subtitle, children: [
970
+ totalNodes,
971
+ " notes • ",
972
+ totalEdges,
973
+ " links"
974
+ ] })
975
+ ] }),
976
+ /* @__PURE__ */ jsxs("span", { className: styles$e.badge, children: [
977
+ nodes.length,
978
+ "/",
979
+ totalNodes
980
+ ] })
981
+ ] }),
982
+ /* @__PURE__ */ jsxs(
983
+ "svg",
984
+ {
985
+ className: styles$e.canvas,
986
+ viewBox: "0 0 200 200",
987
+ role: "img",
988
+ "aria-label": `Mini graph with ${totalNodes} notes and ${totalEdges} links`,
989
+ children: [
990
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("radialGradient", { id: "papyr-mini-graph-glow", cx: "50%", cy: "45%", r: "60%", children: [
991
+ /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: "rgba(99, 102, 241, 0.35)" }),
992
+ /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: "rgba(99, 102, 241, 0)" })
993
+ ] }) }),
994
+ /* @__PURE__ */ jsx(
995
+ "circle",
996
+ {
997
+ className: styles$e.glow,
998
+ cx: "100",
999
+ cy: "100",
1000
+ r: "78"
1001
+ }
1002
+ ),
1003
+ /* @__PURE__ */ jsx("g", { className: styles$e.edges, children: edges.map((edge, index) => /* @__PURE__ */ jsx(
1004
+ "line",
1005
+ {
1006
+ className: styles$e.edge,
1007
+ x1: edge.source.x,
1008
+ y1: edge.source.y,
1009
+ x2: edge.target.x,
1010
+ y2: edge.target.y
1011
+ },
1012
+ `${edge.source.id}-${edge.target.id}-${index}`
1013
+ )) }),
1014
+ /* @__PURE__ */ jsx("g", { className: styles$e.nodes, children: nodes.map((node) => /* @__PURE__ */ jsx(
1015
+ "circle",
1016
+ {
1017
+ className: styles$e.node,
1018
+ cx: node.x,
1019
+ cy: node.y,
1020
+ r: node.radius,
1021
+ children: /* @__PURE__ */ jsx("title", { children: node.label })
1022
+ },
1023
+ node.id
1024
+ )) })
1025
+ ]
1026
+ }
1027
+ ),
1028
+ !nodes.length && /* @__PURE__ */ jsx("div", { className: styles$e.emptyState, children: "No connections yet." })
1029
+ ] });
1030
+ };
1031
+
1032
+ const container$8 = "_container_1vncr_1";
1033
+ const header$1 = "_header_1vncr_10";
1034
+ const breadcrumbs = "_breadcrumbs_1vncr_17";
1035
+ const title$2 = "_title_1vncr_21";
1036
+ const summary = "_summary_1vncr_26";
1037
+ const metaRow = "_metaRow_1vncr_33";
1038
+ const tagList = "_tagList_1vncr_43";
1039
+ const tag$2 = "_tag_1vncr_43";
1040
+ const tagEmpty = "_tagEmpty_1vncr_60";
1041
+ const metaList = "_metaList_1vncr_65";
1042
+ const metaItem = "_metaItem_1vncr_71";
1043
+ const content = "_content_1vncr_90";
1044
+ const codeBlockWrapper = "_codeBlockWrapper_1vncr_119";
1045
+ const codeBlockHeader = "_codeBlockHeader_1vncr_131";
1046
+ const codeBlockLanguage = "_codeBlockLanguage_1vncr_141";
1047
+ const copyButton = "_copyButton_1vncr_149";
1048
+ const codeBlockPre = "_codeBlockPre_1vncr_178";
1049
+ const codeBlockCode = "_codeBlockCode_1vncr_198";
1050
+ const linkedNotes = "_linkedNotes_1vncr_205";
1051
+ const linkedNotesHeader = "_linkedNotesHeader_1vncr_214";
1052
+ const linkedNotesTitle = "_linkedNotesTitle_1vncr_220";
1053
+ const linkedNotesCount = "_linkedNotesCount_1vncr_229";
1054
+ const linkedNotesList = "_linkedNotesList_1vncr_243";
1055
+ const linkedNotesItem = "_linkedNotesItem_1vncr_252";
1056
+ const linkedNoteLink = "_linkedNoteLink_1vncr_257";
1057
+ const styles$d = {
1058
+ container: container$8,
1059
+ header: header$1,
1060
+ breadcrumbs: breadcrumbs,
1061
+ title: title$2,
1062
+ summary: summary,
1063
+ metaRow: metaRow,
1064
+ tagList: tagList,
1065
+ tag: tag$2,
1066
+ tagEmpty: tagEmpty,
1067
+ metaList: metaList,
1068
+ metaItem: metaItem,
1069
+ content: content,
1070
+ codeBlockWrapper: codeBlockWrapper,
1071
+ codeBlockHeader: codeBlockHeader,
1072
+ codeBlockLanguage: codeBlockLanguage,
1073
+ copyButton: copyButton,
1074
+ codeBlockPre: codeBlockPre,
1075
+ codeBlockCode: codeBlockCode,
1076
+ linkedNotes: linkedNotes,
1077
+ linkedNotesHeader: linkedNotesHeader,
1078
+ linkedNotesTitle: linkedNotesTitle,
1079
+ linkedNotesCount: linkedNotesCount,
1080
+ linkedNotesList: linkedNotesList,
1081
+ linkedNotesItem: linkedNotesItem,
1082
+ linkedNoteLink: linkedNoteLink
1083
+ };
1084
+
1085
+ function toForceGraphData(graph) {
1086
+ if (!graph) {
1087
+ return { nodes: [], links: [] };
1088
+ }
1089
+ const nodes = [];
1090
+ const links = [];
1091
+ for (const [, node] of graph.nodes) {
1092
+ nodes.push({
1093
+ id: node.id,
1094
+ label: node.label,
1095
+ value: node.linkCount
1096
+ });
1097
+ }
1098
+ graph.edges.forEach((edge) => {
1099
+ links.push({
1100
+ source: edge.source,
1101
+ target: edge.target,
1102
+ value: 1
1103
+ });
1104
+ });
1105
+ return { nodes, links };
1106
+ }
1107
+
1108
+ function buildWikiLink(slug) {
1109
+ return `[[${slug}]]`;
1110
+ }
1111
+ function findNoteBySlug(notes, slug) {
1112
+ return notes.find((note) => note.slug === slug);
1113
+ }
1114
+ function parseNoteHash(hash) {
1115
+ if (!hash || !hash.startsWith("#")) {
1116
+ return { slug: null, anchor: null };
1117
+ }
1118
+ const fragment = hash.slice(1);
1119
+ if (!fragment) {
1120
+ return { slug: null, anchor: null };
1121
+ }
1122
+ if (fragment.startsWith("/note/")) {
1123
+ const rest = fragment.slice("/note/".length);
1124
+ const anchorIndex = rest.indexOf("#");
1125
+ const slugFragment = anchorIndex === -1 ? rest : rest.slice(0, anchorIndex);
1126
+ const anchorFragment = anchorIndex === -1 ? null : rest.slice(anchorIndex + 1);
1127
+ let slug = slugFragment || null;
1128
+ if (slug) {
1129
+ try {
1130
+ slug = decodeURIComponent(slug);
1131
+ } catch {
1132
+ }
1133
+ }
1134
+ let anchor2 = anchorFragment && anchorFragment.length > 0 ? anchorFragment : null;
1135
+ if (anchor2) {
1136
+ try {
1137
+ anchor2 = decodeURIComponent(anchor2);
1138
+ } catch {
1139
+ }
1140
+ }
1141
+ return { slug, anchor: anchor2 };
1142
+ }
1143
+ if (fragment.startsWith("/")) {
1144
+ return { slug: null, anchor: null };
1145
+ }
1146
+ let anchor = fragment;
1147
+ if (anchor) {
1148
+ try {
1149
+ anchor = decodeURIComponent(anchor);
1150
+ } catch {
1151
+ }
1152
+ }
1153
+ return { slug: null, anchor };
1154
+ }
1155
+ function updateHashWithAnchor(anchor) {
1156
+ if (typeof window === "undefined" || typeof window.history === "undefined") {
1157
+ return;
1158
+ }
1159
+ const trimmed = anchor.trim();
1160
+ const currentHash = window.location.hash ?? "";
1161
+ const { slug } = parseNoteHash(currentHash);
1162
+ let nextHash;
1163
+ if (slug) {
1164
+ const encodedSlug = encodeURIComponent(slug);
1165
+ nextHash = trimmed ? `#/note/${encodedSlug}#${encodeURIComponent(trimmed)}` : `#/note/${encodedSlug}`;
1166
+ } else {
1167
+ nextHash = trimmed ? `#${encodeURIComponent(trimmed)}` : "#";
1168
+ }
1169
+ window.history.replaceState(null, "", nextHash);
1170
+ }
1171
+
1172
+ function isHydratedSearchIndex(value) {
1173
+ const candidate = value;
1174
+ return Boolean(candidate.index && typeof candidate.index.search === "function");
1175
+ }
1176
+ function hydrateSearchIndex(value) {
1177
+ if (isHydratedSearchIndex(value)) {
1178
+ return value;
1179
+ }
1180
+ return importSearchIndex(value);
1181
+ }
1182
+
1183
+ const trigger = "_trigger_j6udx_1";
1184
+ const highlight$1 = "_highlight_j6udx_7";
1185
+ const panel = "_panel_j6udx_13";
1186
+ const styles$c = {
1187
+ trigger: trigger,
1188
+ highlight: highlight$1,
1189
+ panel: panel};
1190
+
1191
+ const HoverPreview = ({
1192
+ trigger,
1193
+ renderPreview,
1194
+ highlightTrigger = true
1195
+ }) => {
1196
+ const [open, setOpen] = useState(false);
1197
+ const { refs, floatingStyles } = useFloating({
1198
+ placement: "top",
1199
+ middleware: [offset(12), flip(), shift()],
1200
+ whileElementsMounted: autoUpdate
1201
+ });
1202
+ const handleKeyDown = useCallback((e) => {
1203
+ if (e.key === "Escape" && open) {
1204
+ setOpen(false);
1205
+ }
1206
+ }, [open]);
1207
+ return /* @__PURE__ */ jsxs(
1208
+ "span",
1209
+ {
1210
+ className: clsx(styles$c.trigger, highlightTrigger && styles$c.highlight),
1211
+ ref: refs.setReference,
1212
+ onMouseEnter: () => setOpen(true),
1213
+ onFocus: () => setOpen(true),
1214
+ onMouseLeave: () => setOpen(false),
1215
+ onBlur: () => setOpen(false),
1216
+ onKeyDown: handleKeyDown,
1217
+ "data-papyr-component": "HoverPreview",
1218
+ role: "button",
1219
+ "aria-haspopup": "dialog",
1220
+ "aria-expanded": open,
1221
+ children: [
1222
+ trigger,
1223
+ open ? /* @__PURE__ */ jsx(
1224
+ "div",
1225
+ {
1226
+ ref: refs.setFloating,
1227
+ style: floatingStyles,
1228
+ className: styles$c.panel,
1229
+ role: "dialog",
1230
+ "aria-modal": "false",
1231
+ "aria-label": "Preview",
1232
+ children: renderPreview()
1233
+ }
1234
+ ) : null
1235
+ ]
1236
+ }
1237
+ );
1238
+ };
1239
+
1240
+ const container$7 = "_container_1raaj_1";
1241
+ const title$1 = "_title_1raaj_8";
1242
+ const excerpt = "_excerpt_1raaj_18";
1243
+ const empty = "_empty_1raaj_25";
1244
+ const tags = "_tags_1raaj_32";
1245
+ const tag$1 = "_tag_1raaj_32";
1246
+ const styles$b = {
1247
+ container: container$7,
1248
+ title: title$1,
1249
+ excerpt: excerpt,
1250
+ empty: empty,
1251
+ tags: tags,
1252
+ tag: tag$1
1253
+ };
1254
+
1255
+ const MAX_PREVIEW_LENGTH = 220;
1256
+ const getPreviewText = (note) => {
1257
+ const source = (note.excerpt ?? note.description ?? "").trim();
1258
+ if (!source) {
1259
+ return "";
1260
+ }
1261
+ if (source.length <= MAX_PREVIEW_LENGTH) {
1262
+ return source;
1263
+ }
1264
+ return `${source.slice(0, MAX_PREVIEW_LENGTH - 1).trimEnd()}…`;
1265
+ };
1266
+ const NotePreviewContent = ({ note }) => {
1267
+ const previewText = getPreviewText(note);
1268
+ return /* @__PURE__ */ jsxs("div", { className: styles$b.container, children: [
1269
+ /* @__PURE__ */ jsx("h3", { className: styles$b.title, children: note.title ?? note.slug }),
1270
+ previewText ? /* @__PURE__ */ jsx("p", { className: styles$b.excerpt, children: previewText }) : /* @__PURE__ */ jsx("p", { className: styles$b.empty, children: "No preview available" }),
1271
+ note.tags?.length ? /* @__PURE__ */ jsx("ul", { className: styles$b.tags, role: "list", "aria-label": "Tags", children: note.tags.slice(0, 4).map((tag) => /* @__PURE__ */ jsxs("li", { className: styles$b.tag, children: [
1272
+ "#",
1273
+ tag
1274
+ ] }, tag)) }) : null
1275
+ ] });
1276
+ };
1277
+
1278
+ const list$1 = "_list_bm9au_1";
1279
+ const item$1 = "_item_bm9au_12";
1280
+ const separator = "_separator_bm9au_23";
1281
+ const link$1 = "_link_bm9au_27";
1282
+ const styles$a = {
1283
+ list: list$1,
1284
+ item: item$1,
1285
+ separator: separator,
1286
+ link: link$1
1287
+ };
1288
+
1289
+ const Breadcrumbs = ({
1290
+ trail,
1291
+ className,
1292
+ separator = "›"
1293
+ }) => {
1294
+ if (!trail.length) {
1295
+ return null;
1296
+ }
1297
+ return /* @__PURE__ */ jsx("nav", { "aria-label": "Breadcrumb", "data-papyr-component": "Breadcrumbs", children: /* @__PURE__ */ jsx("ol", { className: clsx(styles$a.list, className), children: trail.map((item, index) => {
1298
+ const isLast = index === trail.length - 1;
1299
+ return /* @__PURE__ */ jsxs("li", { className: styles$a.item, "aria-current": isLast ? "page" : void 0, children: [
1300
+ item.href && !isLast ? /* @__PURE__ */ jsx("a", { className: styles$a.link, href: item.href, children: item.label }) : /* @__PURE__ */ jsx("span", { children: item.label }),
1301
+ !isLast && /* @__PURE__ */ jsx("span", { className: styles$a.separator, children: separator })
1302
+ ] }, item.label);
1303
+ }) }) });
1304
+ };
1305
+
1306
+ function useNotes(notes) {
1307
+ return useMemo(() => {
1308
+ return [...notes].sort((a, b) => a.title.localeCompare(b.title));
1309
+ }, [notes]);
1310
+ }
1311
+
1312
+ const viewport = "_viewport_1tgm1_1";
1313
+ const toast = "_toast_1tgm1_13";
1314
+ const message = "_message_1tgm1_46";
1315
+ const closeButton = "_closeButton_1tgm1_52";
1316
+ const styles$9 = {
1317
+ viewport: viewport,
1318
+ toast: toast,
1319
+ message: message,
1320
+ closeButton: closeButton
1321
+ };
1322
+
1323
+ const ToastContainer = ({ toasts, onDismiss }) => {
1324
+ if (toasts.length === 0) {
1325
+ return null;
1326
+ }
1327
+ return /* @__PURE__ */ jsx("div", { className: styles$9.viewport, role: "region", "aria-live": "polite", "aria-label": "Notifications", children: toasts.map((toast) => /* @__PURE__ */ jsxs("div", { className: styles$9.toast, role: "status", children: [
1328
+ /* @__PURE__ */ jsx("span", { className: styles$9.message, children: toast.message }),
1329
+ /* @__PURE__ */ jsx(
1330
+ "button",
1331
+ {
1332
+ type: "button",
1333
+ className: styles$9.closeButton,
1334
+ onClick: () => onDismiss(toast.id),
1335
+ "aria-label": "Dismiss notification",
1336
+ children: "Close"
1337
+ }
1338
+ )
1339
+ ] }, toast.id)) });
1340
+ };
1341
+
1342
+ const PapyrContext = createContext(void 0);
1343
+ const PapyrProvider = ({
1344
+ notes,
1345
+ children,
1346
+ searchIndex,
1347
+ graph = null
1348
+ }) => {
1349
+ const sortedNotes = useNotes(notes);
1350
+ const memoizedGraph = useMemo(() => graph ?? null, [graph]);
1351
+ const [toasts, setToasts] = useState([]);
1352
+ const timersRef = useRef({});
1353
+ const dismissToast = useCallback((id) => {
1354
+ setToasts((prev) => prev.filter((toast) => toast.id !== id));
1355
+ if (typeof window !== "undefined") {
1356
+ const timerId = timersRef.current[id];
1357
+ if (typeof timerId === "number") {
1358
+ window.clearTimeout(timerId);
1359
+ }
1360
+ }
1361
+ delete timersRef.current[id];
1362
+ }, []);
1363
+ const showToast = useCallback(
1364
+ (message, options) => {
1365
+ const id = options?.id ?? `papyr-toast-${Date.now()}-${Math.round(Math.random() * 1e6)}`;
1366
+ const duration = options?.duration ?? 3e3;
1367
+ setToasts((prev) => [
1368
+ ...prev.filter((toast) => toast.id !== id),
1369
+ { id, message }
1370
+ ]);
1371
+ if (typeof window !== "undefined" && duration > 0) {
1372
+ const existingTimeout = timersRef.current[id];
1373
+ if (typeof existingTimeout === "number") {
1374
+ window.clearTimeout(existingTimeout);
1375
+ }
1376
+ const timeoutId = window.setTimeout(() => dismissToast(id), duration);
1377
+ timersRef.current[id] = timeoutId;
1378
+ }
1379
+ return id;
1380
+ },
1381
+ [dismissToast]
1382
+ );
1383
+ const search = useCallback(
1384
+ (query, options) => {
1385
+ return searchNotes(query, searchIndex, options);
1386
+ },
1387
+ [searchIndex]
1388
+ );
1389
+ useEffect(() => {
1390
+ return () => {
1391
+ if (typeof window !== "undefined") {
1392
+ Object.values(timersRef.current).forEach((timerId) => {
1393
+ if (typeof timerId === "number") {
1394
+ window.clearTimeout(timerId);
1395
+ }
1396
+ });
1397
+ }
1398
+ timersRef.current = {};
1399
+ };
1400
+ }, []);
1401
+ const value = useMemo(
1402
+ () => ({
1403
+ notes: sortedNotes,
1404
+ graph: memoizedGraph,
1405
+ searchIndex,
1406
+ search,
1407
+ showToast,
1408
+ dismissToast
1409
+ }),
1410
+ [sortedNotes, memoizedGraph, searchIndex, search, showToast, dismissToast]
1411
+ );
1412
+ return /* @__PURE__ */ jsxs(PapyrContext.Provider, { value, children: [
1413
+ children,
1414
+ /* @__PURE__ */ jsx(ToastContainer, { toasts, onDismiss: dismissToast })
1415
+ ] });
1416
+ };
1417
+ function usePapyr() {
1418
+ const context = useContext(PapyrContext);
1419
+ if (!context) {
1420
+ throw new Error("usePapyr must be used within a PapyrProvider");
1421
+ }
1422
+ return context;
1423
+ }
1424
+ function useOptionalPapyr() {
1425
+ return useContext(PapyrContext);
1426
+ }
1427
+
1428
+ const NOTE_LINK_PREFIX = "#/note/";
1429
+ const buildNoteHash = (slug, fragment) => {
1430
+ const base = `${NOTE_LINK_PREFIX}${encodeURIComponent(slug)}`;
1431
+ return fragment && fragment.length > 0 ? `${base}#${fragment}` : base;
1432
+ };
1433
+ const cloneElementAttributes = (element, exclude = []) => {
1434
+ const excludeSet = new Set(exclude);
1435
+ return Array.from(element.attributes).filter((attr) => !excludeSet.has(attr.name)).map((attr) => ({ name: attr.name, value: attr.value }));
1436
+ };
1437
+ const InlinePreviewAnchor = ({
1438
+ attributes,
1439
+ href,
1440
+ innerHtml,
1441
+ onClick
1442
+ }) => {
1443
+ const anchorRef = useRef(null);
1444
+ useLayoutEffect(() => {
1445
+ const element = anchorRef.current;
1446
+ if (!element) {
1447
+ return;
1448
+ }
1449
+ attributes.forEach((attr) => {
1450
+ if (attr.name === "href") {
1451
+ return;
1452
+ }
1453
+ element.setAttribute(attr.name, attr.value);
1454
+ });
1455
+ element.setAttribute("href", href);
1456
+ }, [attributes, href]);
1457
+ return /* @__PURE__ */ jsx(
1458
+ "a",
1459
+ {
1460
+ ref: anchorRef,
1461
+ href,
1462
+ onClick,
1463
+ dangerouslySetInnerHTML: { __html: innerHtml }
1464
+ }
1465
+ );
1466
+ };
1467
+ const safeDecode = (value) => {
1468
+ try {
1469
+ return decodeURIComponent(value);
1470
+ } catch {
1471
+ return value;
1472
+ }
1473
+ };
1474
+ const NoteViewer = ({ note, emptyState, onNavigateNote }) => {
1475
+ const contentRef = useRef(null);
1476
+ const inlinePreviewCleanupRef = useRef([]);
1477
+ const papyr = useOptionalPapyr();
1478
+ const providerNotes = papyr?.notes ?? [];
1479
+ const linkedNotesHeadingId = note ? `papyr-linked-notes-${note.slug}` : "papyr-linked-notes";
1480
+ const metadataRecord = note?.metadata ?? {};
1481
+ const noteLookup = useMemo(() => {
1482
+ if (!providerNotes.length) {
1483
+ return {};
1484
+ }
1485
+ return providerNotes.reduce((acc, current) => {
1486
+ acc[current.slug] = current;
1487
+ return acc;
1488
+ }, {});
1489
+ }, [providerNotes]);
1490
+ const resolveLinkedSlug = useCallback(
1491
+ (rawTarget) => {
1492
+ if (!rawTarget) {
1493
+ return null;
1494
+ }
1495
+ const baseTarget = rawTarget.split("#")[0];
1496
+ if (noteLookup[baseTarget]) {
1497
+ return baseTarget;
1498
+ }
1499
+ return null;
1500
+ },
1501
+ [noteLookup]
1502
+ );
1503
+ const getPreviewData = useCallback((targetNote) => ({
1504
+ slug: targetNote.slug,
1505
+ title: targetNote.title,
1506
+ excerpt: targetNote.excerpt ?? null,
1507
+ description: targetNote.description ?? null,
1508
+ tags: targetNote.tags ?? []
1509
+ }), []);
1510
+ const linkedNotes = useMemo(() => {
1511
+ if (!note?.linksTo?.length) {
1512
+ return [];
1513
+ }
1514
+ const seen = /* @__PURE__ */ new Set();
1515
+ const previews = [];
1516
+ note.linksTo.forEach((target) => {
1517
+ const resolvedSlug = resolveLinkedSlug(target);
1518
+ if (!resolvedSlug || seen.has(resolvedSlug)) {
1519
+ return;
1520
+ }
1521
+ const targetNote = noteLookup[resolvedSlug];
1522
+ if (!targetNote) {
1523
+ return;
1524
+ }
1525
+ seen.add(resolvedSlug);
1526
+ previews.push(getPreviewData(targetNote));
1527
+ });
1528
+ return previews;
1529
+ }, [note?.linksTo, noteLookup, resolveLinkedSlug, getPreviewData]);
1530
+ const breadcrumbTrail = useMemo(() => {
1531
+ if (!note) {
1532
+ return [];
1533
+ }
1534
+ const rawTrail = metadataRecord.breadcrumbs;
1535
+ if (Array.isArray(rawTrail)) {
1536
+ const normalized = rawTrail.map((item) => {
1537
+ if (typeof item === "string") {
1538
+ return { label: item };
1539
+ }
1540
+ if (item && typeof item === "object") {
1541
+ const label = "label" in item ? String(item.label ?? "") : "";
1542
+ if (!label) {
1543
+ return null;
1544
+ }
1545
+ const href = typeof item.href === "string" ? item.href : void 0;
1546
+ return { label, href };
1547
+ }
1548
+ return null;
1549
+ }).filter((item) => Boolean(item));
1550
+ if (normalized.length) {
1551
+ return normalized;
1552
+ }
1553
+ }
1554
+ const pathSource = typeof metadataRecord.path === "string" ? metadataRecord.path : typeof metadataRecord.filePath === "string" ? metadataRecord.filePath : typeof metadataRecord.relativePath === "string" ? metadataRecord.relativePath : null;
1555
+ if (!pathSource) {
1556
+ return [];
1557
+ }
1558
+ const normalizedPath = pathSource.replace(/\\/g, "/").replace(/^\.\//, "");
1559
+ const segments = normalizedPath.split("/").filter(Boolean);
1560
+ if (!segments.length) {
1561
+ return [];
1562
+ }
1563
+ return segments.map((segment, index) => {
1564
+ const isLast = index === segments.length - 1;
1565
+ const stripped = isLast ? segment.replace(/\.(md|markdown)$/i, "") : segment;
1566
+ const label = stripped.replace(/[-_]/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
1567
+ return { label };
1568
+ });
1569
+ }, [metadataRecord, note]);
1570
+ const metaItems = useMemo(() => {
1571
+ if (!note) {
1572
+ return [];
1573
+ }
1574
+ const items = [];
1575
+ if (note.createdAt) {
1576
+ items.push({ label: "Created", value: note.createdAt });
1577
+ }
1578
+ if (note.updatedAt) {
1579
+ items.push({ label: "Updated", value: note.updatedAt });
1580
+ }
1581
+ if (typeof note.wordCount === "number" && note.wordCount > 0) {
1582
+ items.push({ label: "Words", value: String(note.wordCount) });
1583
+ }
1584
+ if (typeof note.readingTime === "number" && note.readingTime > 0) {
1585
+ items.push({ label: "Read", value: `${note.readingTime} min` });
1586
+ }
1587
+ return items;
1588
+ }, [note]);
1589
+ const dispatchHashChange = useCallback(() => {
1590
+ if (typeof window === "undefined") {
1591
+ return;
1592
+ }
1593
+ const event = typeof window.HashChangeEvent === "function" ? new HashChangeEvent("hashchange") : new Event("hashchange");
1594
+ window.dispatchEvent(event);
1595
+ }, []);
1596
+ const handleNoteLinkNavigation = useCallback(
1597
+ (event, slug, fragment) => {
1598
+ if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.altKey || event.shiftKey || event.currentTarget?.target && event.currentTarget.target !== "_self") {
1599
+ return;
1600
+ }
1601
+ event.preventDefault();
1602
+ if (typeof window === "undefined") {
1603
+ return;
1604
+ }
1605
+ if (onNavigateNote) {
1606
+ onNavigateNote(slug, fragment ? { fragment } : void 0);
1607
+ }
1608
+ const targetHash = buildNoteHash(slug, fragment);
1609
+ if (window.location.hash !== targetHash) {
1610
+ window.history.pushState(null, "", targetHash);
1611
+ }
1612
+ dispatchHashChange();
1613
+ },
1614
+ [dispatchHashChange, onNavigateNote]
1615
+ );
1616
+ useEffect(() => {
1617
+ const container = contentRef.current;
1618
+ if (!container || !note) {
1619
+ return;
1620
+ }
1621
+ const cleanupFns = [];
1622
+ const codeBlocks = Array.from(container.querySelectorAll("pre"));
1623
+ codeBlocks.forEach((pre) => {
1624
+ if (pre.dataset.papyrEnhanced === "true") {
1625
+ return;
1626
+ }
1627
+ const code = pre.querySelector("code");
1628
+ if (!code) {
1629
+ return;
1630
+ }
1631
+ const wrapper = document.createElement("div");
1632
+ wrapper.classList.add(styles$d.codeBlockWrapper);
1633
+ wrapper.setAttribute("data-papyr-code-wrapper", "true");
1634
+ const header = document.createElement("div");
1635
+ header.classList.add(styles$d.codeBlockHeader);
1636
+ const languageClass = Array.from(code.classList).find((cls) => cls.startsWith("language-"));
1637
+ const detectedLanguage = languageClass ? languageClass.replace("language-", "") : code.getAttribute("data-language") || void 0;
1638
+ const language = detectedLanguage && detectedLanguage.trim() || "code";
1639
+ const label = document.createElement("span");
1640
+ label.classList.add(styles$d.codeBlockLanguage);
1641
+ label.textContent = language.toUpperCase();
1642
+ header.appendChild(label);
1643
+ const button = document.createElement("button");
1644
+ button.type = "button";
1645
+ button.classList.add(styles$d.copyButton);
1646
+ button.setAttribute("data-papyr-copy-button", "true");
1647
+ button.setAttribute("aria-label", "Copy code block");
1648
+ button.title = "Copy code";
1649
+ let resetTimer;
1650
+ const setButtonState = (state, autoReset) => {
1651
+ window.clearTimeout(resetTimer);
1652
+ button.dataset.state = state;
1653
+ switch (state) {
1654
+ case "idle":
1655
+ button.textContent = "Copy";
1656
+ break;
1657
+ case "copied":
1658
+ button.textContent = "Copied ✓";
1659
+ if (autoReset) {
1660
+ resetTimer = window.setTimeout(() => setButtonState("idle", false), 2e3);
1661
+ }
1662
+ break;
1663
+ case "empty":
1664
+ button.textContent = "Empty";
1665
+ if (autoReset) {
1666
+ resetTimer = window.setTimeout(() => setButtonState("idle", false), 1500);
1667
+ }
1668
+ break;
1669
+ case "error":
1670
+ button.textContent = "Copy Failed";
1671
+ if (autoReset) {
1672
+ resetTimer = window.setTimeout(() => setButtonState("idle", false), 2e3);
1673
+ }
1674
+ break;
1675
+ }
1676
+ };
1677
+ const fallbackCopy = (text) => {
1678
+ const textarea = document.createElement("textarea");
1679
+ textarea.value = text;
1680
+ textarea.setAttribute("readonly", "");
1681
+ textarea.style.position = "absolute";
1682
+ textarea.style.left = "-9999px";
1683
+ textarea.style.top = "0";
1684
+ textarea.style.opacity = "0";
1685
+ document.body.appendChild(textarea);
1686
+ try {
1687
+ textarea.focus({ preventScroll: true });
1688
+ } catch (focusError) {
1689
+ textarea.focus();
1690
+ }
1691
+ textarea.select();
1692
+ try {
1693
+ textarea.setSelectionRange(0, textarea.value.length);
1694
+ } catch (selectionError) {
1695
+ }
1696
+ const successful = document.execCommand("copy");
1697
+ document.body.removeChild(textarea);
1698
+ if (!successful) {
1699
+ throw new Error("execCommand returned false");
1700
+ }
1701
+ };
1702
+ const handleCopy = async (event) => {
1703
+ event.preventDefault();
1704
+ event.stopPropagation();
1705
+ const text = (code.innerText ?? code.textContent ?? "").replace(/\u00a0/g, " ");
1706
+ if (!text.trim()) {
1707
+ setButtonState("empty", true);
1708
+ return;
1709
+ }
1710
+ try {
1711
+ if (navigator.clipboard?.writeText) {
1712
+ await navigator.clipboard.writeText(text);
1713
+ } else {
1714
+ fallbackCopy(text);
1715
+ }
1716
+ setButtonState("copied", true);
1717
+ } catch (error) {
1718
+ console.warn("Failed to copy code block", error);
1719
+ try {
1720
+ fallbackCopy(text);
1721
+ setButtonState("copied", true);
1722
+ } catch (fallbackError) {
1723
+ console.warn("Fallback copy failed", fallbackError);
1724
+ setButtonState("error", true);
1725
+ }
1726
+ }
1727
+ };
1728
+ setButtonState("idle", false);
1729
+ button.addEventListener("click", handleCopy);
1730
+ cleanupFns.push(() => {
1731
+ button.removeEventListener("click", handleCopy);
1732
+ window.clearTimeout(resetTimer);
1733
+ });
1734
+ header.appendChild(button);
1735
+ pre.parentElement?.insertBefore(wrapper, pre);
1736
+ wrapper.appendChild(header);
1737
+ wrapper.appendChild(pre);
1738
+ pre.classList.add(styles$d.codeBlockPre);
1739
+ code.classList.add(styles$d.codeBlockCode);
1740
+ pre.dataset.papyrEnhanced = "true";
1741
+ });
1742
+ const anchorLinks = Array.from(container.querySelectorAll("a"));
1743
+ const handleAnchorClick = (event) => {
1744
+ const anchor = event.currentTarget;
1745
+ const href = anchor.getAttribute("href") ?? "";
1746
+ if (!href.startsWith("#") || href.startsWith("#/")) {
1747
+ return;
1748
+ }
1749
+ const targetId = href.slice(1);
1750
+ if (!targetId) {
1751
+ return;
1752
+ }
1753
+ event.preventDefault();
1754
+ const targetElement = document.getElementById(targetId);
1755
+ if (targetElement) {
1756
+ targetElement.scrollIntoView({ behavior: "smooth", block: "start" });
1757
+ }
1758
+ updateHashWithAnchor(targetId);
1759
+ };
1760
+ anchorLinks.forEach((link) => {
1761
+ const href = link.getAttribute("href") ?? "";
1762
+ if (href.startsWith("#") && !href.startsWith("#/")) {
1763
+ link.addEventListener("click", handleAnchorClick);
1764
+ cleanupFns.push(() => {
1765
+ link.removeEventListener("click", handleAnchorClick);
1766
+ });
1767
+ }
1768
+ });
1769
+ if (typeof window !== "undefined") {
1770
+ const hash = window.location.hash ?? "";
1771
+ const { slug: hashSlug, anchor: hashAnchor } = parseNoteHash(hash);
1772
+ if (hashAnchor && (!hashSlug || hashSlug === note.slug)) {
1773
+ const targetElement = document.getElementById(hashAnchor);
1774
+ if (targetElement) {
1775
+ window.requestAnimationFrame(() => {
1776
+ targetElement.scrollIntoView({ behavior: "smooth", block: "start" });
1777
+ });
1778
+ }
1779
+ }
1780
+ }
1781
+ return () => {
1782
+ cleanupFns.forEach((fn) => fn());
1783
+ };
1784
+ }, [note?.slug, note?.html]);
1785
+ useEffect(() => {
1786
+ if (typeof window === "undefined") {
1787
+ return;
1788
+ }
1789
+ inlinePreviewCleanupRef.current.forEach((cleanup) => cleanup());
1790
+ inlinePreviewCleanupRef.current = [];
1791
+ const container = contentRef.current;
1792
+ if (!container || !note) {
1793
+ return;
1794
+ }
1795
+ const inlineLinks = Array.from(
1796
+ container.querySelectorAll('a[href^="#/note/"]')
1797
+ );
1798
+ inlineLinks.forEach((anchor) => {
1799
+ const href = anchor.getAttribute("href") ?? "";
1800
+ if (!href.startsWith(NOTE_LINK_PREFIX)) {
1801
+ return;
1802
+ }
1803
+ if (anchor.closest('[data-papyr-inline-preview="true"]')) {
1804
+ return;
1805
+ }
1806
+ const rawTarget = href.slice(NOTE_LINK_PREFIX.length);
1807
+ const [rawSlugPart, ...fragmentParts] = rawTarget.split("#");
1808
+ const fragment = fragmentParts.length > 0 ? fragmentParts.join("#").trim() || void 0 : void 0;
1809
+ const decodedTarget = safeDecode(rawSlugPart);
1810
+ const resolvedSlug = resolveLinkedSlug(decodedTarget);
1811
+ if (!resolvedSlug) {
1812
+ return;
1813
+ }
1814
+ const targetNote = noteLookup[resolvedSlug];
1815
+ if (!targetNote) {
1816
+ return;
1817
+ }
1818
+ const parent = anchor.parentElement;
1819
+ if (!parent) {
1820
+ return;
1821
+ }
1822
+ const wrapper = document.createElement("span");
1823
+ wrapper.setAttribute("data-papyr-inline-preview", "true");
1824
+ parent.replaceChild(wrapper, anchor);
1825
+ const anchorHtml = anchor.innerHTML;
1826
+ const preservedAttributes = cloneElementAttributes(anchor, ["href"]);
1827
+ const previewData = getPreviewData(targetNote);
1828
+ const noteHref = buildNoteHash(resolvedSlug, fragment);
1829
+ const root = createRoot(wrapper);
1830
+ root.render(
1831
+ /* @__PURE__ */ jsx(
1832
+ HoverPreview,
1833
+ {
1834
+ highlightTrigger: false,
1835
+ trigger: /* @__PURE__ */ jsx(
1836
+ InlinePreviewAnchor,
1837
+ {
1838
+ attributes: preservedAttributes,
1839
+ href: noteHref,
1840
+ innerHtml: anchorHtml,
1841
+ onClick: (event) => handleNoteLinkNavigation(event, resolvedSlug, fragment)
1842
+ }
1843
+ ),
1844
+ renderPreview: () => /* @__PURE__ */ jsx(NotePreviewContent, { note: previewData })
1845
+ }
1846
+ )
1847
+ );
1848
+ inlinePreviewCleanupRef.current.push(() => {
1849
+ root.unmount();
1850
+ wrapper.replaceWith(anchor);
1851
+ });
1852
+ });
1853
+ return () => {
1854
+ inlinePreviewCleanupRef.current.forEach((cleanup) => cleanup());
1855
+ inlinePreviewCleanupRef.current = [];
1856
+ };
1857
+ }, [
1858
+ note?.slug,
1859
+ note?.html,
1860
+ resolveLinkedSlug,
1861
+ noteLookup,
1862
+ handleNoteLinkNavigation,
1863
+ getPreviewData
1864
+ ]);
1865
+ const hasLinkedNotes = linkedNotes.length > 0;
1866
+ if (!note) {
1867
+ return emptyState ? /* @__PURE__ */ jsx(Fragment, { children: emptyState }) : null;
1868
+ }
1869
+ return /* @__PURE__ */ jsxs("article", { className: styles$d.container, "data-papyr-component": "NoteViewer", children: [
1870
+ /* @__PURE__ */ jsxs("header", { className: styles$d.header, children: [
1871
+ breadcrumbTrail.length > 0 && /* @__PURE__ */ jsx(Breadcrumbs, { trail: breadcrumbTrail, className: styles$d.breadcrumbs }),
1872
+ /* @__PURE__ */ jsx("h1", { className: styles$d.title, children: note.title }),
1873
+ note.description && /* @__PURE__ */ jsx("p", { className: styles$d.summary, children: note.description }),
1874
+ /* @__PURE__ */ jsxs("div", { className: styles$d.metaRow, children: [
1875
+ /* @__PURE__ */ jsx("div", { className: styles$d.tagList, children: note.tags?.length ? note.tags.map((tag) => /* @__PURE__ */ jsx("span", { className: styles$d.tag, children: tag }, tag)) : /* @__PURE__ */ jsx("span", { className: styles$d.tagEmpty, children: "No tags assigned" }) }),
1876
+ metaItems.length > 0 && /* @__PURE__ */ jsx("dl", { className: styles$d.metaList, children: metaItems.map((item) => /* @__PURE__ */ jsxs("div", { className: styles$d.metaItem, children: [
1877
+ /* @__PURE__ */ jsx("dt", { children: item.label }),
1878
+ /* @__PURE__ */ jsx("dd", { children: item.value })
1879
+ ] }, item.label)) })
1880
+ ] })
1881
+ ] }),
1882
+ /* @__PURE__ */ jsx(
1883
+ "section",
1884
+ {
1885
+ ref: contentRef,
1886
+ className: styles$d.content,
1887
+ dangerouslySetInnerHTML: { __html: note.html }
1888
+ }
1889
+ ),
1890
+ hasLinkedNotes ? /* @__PURE__ */ jsxs(
1891
+ "section",
1892
+ {
1893
+ className: styles$d.linkedNotes,
1894
+ "aria-labelledby": linkedNotesHeadingId,
1895
+ "data-papyr-component": "LinkedNotesPreview",
1896
+ children: [
1897
+ /* @__PURE__ */ jsxs("div", { className: styles$d.linkedNotesHeader, children: [
1898
+ /* @__PURE__ */ jsx("h2", { id: linkedNotesHeadingId, className: styles$d.linkedNotesTitle, children: "Linked notes" }),
1899
+ /* @__PURE__ */ jsx("span", { className: styles$d.linkedNotesCount, children: linkedNotes.length })
1900
+ ] }),
1901
+ /* @__PURE__ */ jsx("ul", { className: styles$d.linkedNotesList, children: linkedNotes.map((related) => /* @__PURE__ */ jsx("li", { className: styles$d.linkedNotesItem, children: /* @__PURE__ */ jsx(
1902
+ HoverPreview,
1903
+ {
1904
+ highlightTrigger: false,
1905
+ trigger: /* @__PURE__ */ jsx(
1906
+ "a",
1907
+ {
1908
+ href: buildNoteHash(related.slug),
1909
+ className: styles$d.linkedNoteLink,
1910
+ onClick: (event) => handleNoteLinkNavigation(event, related.slug),
1911
+ children: related.title ?? related.slug
1912
+ }
1913
+ ),
1914
+ renderPreview: () => /* @__PURE__ */ jsx(NotePreviewContent, { note: related })
1915
+ }
1916
+ ) }, related.slug)) })
1917
+ ]
1918
+ }
1919
+ ) : null
1920
+ ] });
1921
+ };
1922
+
1923
+ const wrapper = "_wrapper_1suuy_1";
1924
+ const input$2 = "_input_1suuy_6";
1925
+ const styles$8 = {
1926
+ wrapper: wrapper,
1927
+ input: input$2
1928
+ };
1929
+
1930
+ const SearchBar = ({
1931
+ query,
1932
+ onQueryChange,
1933
+ placeholder = "Search notes…",
1934
+ className
1935
+ }) => {
1936
+ const handleChange = (event) => {
1937
+ onQueryChange(event.target.value);
1938
+ };
1939
+ return /* @__PURE__ */ jsx("div", { className: clsx(styles$8.wrapper, className), "data-papyr-component": "SearchBar", children: /* @__PURE__ */ jsx(
1940
+ "input",
1941
+ {
1942
+ className: styles$8.input,
1943
+ value: query,
1944
+ onChange: handleChange,
1945
+ placeholder,
1946
+ type: "search",
1947
+ autoComplete: "off",
1948
+ "aria-label": "Search notes"
1949
+ }
1950
+ ) });
1951
+ };
1952
+
1953
+ const container$6 = "_container_r50hu_1";
1954
+ const tag = "_tag_r50hu_7";
1955
+ const tagActive = "_tagActive_r50hu_30";
1956
+ const styles$7 = {
1957
+ container: container$6,
1958
+ tag: tag,
1959
+ tagActive: tagActive
1960
+ };
1961
+
1962
+ const TagFilter = ({
1963
+ availableTags,
1964
+ selectedTags,
1965
+ onChange,
1966
+ className
1967
+ }) => {
1968
+ const toggle = (tag) => {
1969
+ if (selectedTags.includes(tag)) {
1970
+ onChange(selectedTags.filter((existing) => existing !== tag));
1971
+ } else {
1972
+ onChange([...selectedTags, tag]);
1973
+ }
1974
+ };
1975
+ if (!availableTags.length) {
1976
+ return null;
1977
+ }
1978
+ return /* @__PURE__ */ jsx(
1979
+ "div",
1980
+ {
1981
+ className: clsx(styles$7.container, className),
1982
+ "data-papyr-component": "TagFilter",
1983
+ role: "group",
1984
+ "aria-label": "Filter by tags",
1985
+ children: availableTags.map((tag) => /* @__PURE__ */ jsxs(
1986
+ "button",
1987
+ {
1988
+ type: "button",
1989
+ className: clsx(styles$7.tag, selectedTags.includes(tag) && styles$7.tagActive),
1990
+ onClick: () => toggle(tag),
1991
+ "aria-pressed": selectedTags.includes(tag),
1992
+ children: [
1993
+ "#",
1994
+ tag
1995
+ ]
1996
+ },
1997
+ tag
1998
+ ))
1999
+ }
2000
+ );
2001
+ };
2002
+
2003
+ const container$5 = "_container_vip00_1";
2004
+ const input$1 = "_input_vip00_7";
2005
+ const dropdown = "_dropdown_vip00_24";
2006
+ const category = "_category_vip00_39";
2007
+ const categoryTitle = "_categoryTitle_vip00_43";
2008
+ const resultItem = "_resultItem_vip00_54";
2009
+ const resultItemSelected = "_resultItemSelected_vip00_67";
2010
+ const resultTitle = "_resultTitle_vip00_71";
2011
+ const resultExcerpt = "_resultExcerpt_vip00_75";
2012
+ const titleMatch = "_titleMatch_vip00_80";
2013
+ const headingMatch = "_headingMatch_vip00_84";
2014
+ const highlight = "_highlight_vip00_110";
2015
+ const styles$6 = {
2016
+ container: container$5,
2017
+ input: input$1,
2018
+ dropdown: dropdown,
2019
+ category: category,
2020
+ categoryTitle: categoryTitle,
2021
+ resultItem: resultItem,
2022
+ resultItemSelected: resultItemSelected,
2023
+ resultTitle: resultTitle,
2024
+ resultExcerpt: resultExcerpt,
2025
+ titleMatch: titleMatch,
2026
+ headingMatch: headingMatch,
2027
+ highlight: highlight
2028
+ };
2029
+
2030
+ const SearchDropdown = ({
2031
+ searchIndex,
2032
+ onResultClick,
2033
+ placeholder = "Search...",
2034
+ className,
2035
+ maxResults = 20,
2036
+ showCategories = true
2037
+ }) => {
2038
+ const [query, setQuery] = useState("");
2039
+ const [results, setResults] = useState([]);
2040
+ const [categorizedResults, setCategorizedResults] = useState({
2041
+ titleMatches: [],
2042
+ headingMatches: [],
2043
+ contentMatches: []
2044
+ });
2045
+ const [isOpen, setIsOpen] = useState(false);
2046
+ const [selectedIndex, setSelectedIndex] = useState(0);
2047
+ const inputRef = useRef(null);
2048
+ const dropdownRef = useRef(null);
2049
+ useEffect(() => {
2050
+ if (!query.trim()) {
2051
+ setResults([]);
2052
+ setCategorizedResults({ titleMatches: [], headingMatches: [], contentMatches: [] });
2053
+ setIsOpen(false);
2054
+ return;
2055
+ }
2056
+ const searchResults = searchNotes(query, searchIndex, {
2057
+ limit: maxResults,
2058
+ highlight: true,
2059
+ fuzzy: true,
2060
+ boost: { title: 5, headings: 3, content: 1, metadata: 2 }
2061
+ // Boost titles and headings
2062
+ });
2063
+ const titleMatches = searchResults.filter(
2064
+ (result) => result.matchedFields.includes("title")
2065
+ );
2066
+ const headingMatches = searchResults.filter(
2067
+ (result) => !result.matchedFields.includes("title") && result.matchedFields.includes("headings")
2068
+ );
2069
+ const contentMatches = searchResults.filter(
2070
+ (result) => !result.matchedFields.includes("title") && !result.matchedFields.includes("headings")
2071
+ );
2072
+ const orderedResults = showCategories ? [...titleMatches, ...headingMatches, ...contentMatches] : searchResults;
2073
+ setResults(orderedResults);
2074
+ setCategorizedResults({ titleMatches, headingMatches, contentMatches });
2075
+ setIsOpen(orderedResults.length > 0);
2076
+ setSelectedIndex(0);
2077
+ }, [query, searchIndex, maxResults, showCategories]);
2078
+ const handleKeyDown = (event) => {
2079
+ if (!isOpen || results.length === 0) return;
2080
+ switch (event.key) {
2081
+ case "ArrowDown":
2082
+ event.preventDefault();
2083
+ setSelectedIndex((prev) => (prev + 1) % results.length);
2084
+ break;
2085
+ case "ArrowUp":
2086
+ event.preventDefault();
2087
+ setSelectedIndex((prev) => (prev - 1 + results.length) % results.length);
2088
+ break;
2089
+ case "Enter":
2090
+ event.preventDefault();
2091
+ if (results[selectedIndex]) {
2092
+ onResultClick(results[selectedIndex].slug);
2093
+ setIsOpen(false);
2094
+ }
2095
+ break;
2096
+ case "Escape":
2097
+ setIsOpen(false);
2098
+ inputRef.current?.blur();
2099
+ break;
2100
+ }
2101
+ };
2102
+ useEffect(() => {
2103
+ const handleClickOutside = (event) => {
2104
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target) && inputRef.current && !inputRef.current.contains(event.target)) {
2105
+ setIsOpen(false);
2106
+ }
2107
+ };
2108
+ document.addEventListener("mousedown", handleClickOutside);
2109
+ return () => document.removeEventListener("mousedown", handleClickOutside);
2110
+ }, []);
2111
+ const highlightText = (text, query2) => {
2112
+ if (!query2.trim()) return text;
2113
+ const regex = new RegExp(`(${query2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`, "gi");
2114
+ const parts = text.split(regex);
2115
+ return parts.map(
2116
+ (part, index) => regex.test(part) ? /* @__PURE__ */ jsx("mark", { className: styles$6.highlight, children: part }, index) : part
2117
+ );
2118
+ };
2119
+ const renderResult = (result, index) => {
2120
+ const isSelected = index === selectedIndex;
2121
+ const isTitleMatch = result.matchedFields.includes("title");
2122
+ const isHeadingMatch = !isTitleMatch && result.matchedFields.includes("headings");
2123
+ const headingHighlight = result.highlights?.find((highlight) => highlight.field === "headings");
2124
+ const excerptText = isHeadingMatch ? headingHighlight?.text ?? result.excerpt : result.excerpt;
2125
+ return /* @__PURE__ */ jsxs(
2126
+ "div",
2127
+ {
2128
+ id: `result-${result.slug}`,
2129
+ role: "option",
2130
+ "aria-selected": isSelected,
2131
+ className: clsx(
2132
+ styles$6.resultItem,
2133
+ isSelected && styles$6.resultItemSelected,
2134
+ isTitleMatch && styles$6.titleMatch,
2135
+ isHeadingMatch && styles$6.headingMatch
2136
+ ),
2137
+ onClick: () => {
2138
+ onResultClick(result.slug);
2139
+ setIsOpen(false);
2140
+ },
2141
+ children: [
2142
+ /* @__PURE__ */ jsx("div", { className: styles$6.resultTitle, children: highlightText(result.title, query) }),
2143
+ excerptText && /* @__PURE__ */ jsx("div", { className: styles$6.resultExcerpt, children: highlightText(excerptText, query) })
2144
+ ]
2145
+ },
2146
+ result.slug
2147
+ );
2148
+ };
2149
+ return /* @__PURE__ */ jsxs("div", { className: clsx(styles$6.container, className), children: [
2150
+ /* @__PURE__ */ jsx(
2151
+ "input",
2152
+ {
2153
+ ref: inputRef,
2154
+ type: "search",
2155
+ value: query,
2156
+ onChange: (e) => setQuery(e.target.value),
2157
+ onKeyDown: handleKeyDown,
2158
+ onFocus: () => query.trim() && setIsOpen(true),
2159
+ placeholder,
2160
+ className: styles$6.input,
2161
+ autoComplete: "off",
2162
+ "aria-label": "Search",
2163
+ role: "combobox",
2164
+ "aria-expanded": isOpen,
2165
+ "aria-controls": "search-results",
2166
+ "aria-activedescendant": isOpen && results[selectedIndex] ? `result-${results[selectedIndex].slug}` : void 0,
2167
+ "aria-autocomplete": "list"
2168
+ }
2169
+ ),
2170
+ isOpen && results.length > 0 && /* @__PURE__ */ jsx("div", { ref: dropdownRef, className: styles$6.dropdown, id: "search-results", role: "listbox", children: showCategories ? /* @__PURE__ */ jsxs(Fragment, { children: [
2171
+ categorizedResults.titleMatches.length > 0 && /* @__PURE__ */ jsxs("div", { className: styles$6.category, children: [
2172
+ /* @__PURE__ */ jsx("div", { className: styles$6.categoryTitle, children: "Notes" }),
2173
+ categorizedResults.titleMatches.map(
2174
+ (result, index) => renderResult(result, index)
2175
+ )
2176
+ ] }),
2177
+ categorizedResults.headingMatches.length > 0 && /* @__PURE__ */ jsxs("div", { className: styles$6.category, children: [
2178
+ /* @__PURE__ */ jsx("div", { className: styles$6.categoryTitle, children: "Headers" }),
2179
+ categorizedResults.headingMatches.map(
2180
+ (result, index) => renderResult(result, categorizedResults.titleMatches.length + index)
2181
+ )
2182
+ ] }),
2183
+ categorizedResults.contentMatches.length > 0 && /* @__PURE__ */ jsxs("div", { className: styles$6.category, children: [
2184
+ /* @__PURE__ */ jsx("div", { className: styles$6.categoryTitle, children: "Content" }),
2185
+ categorizedResults.contentMatches.map(
2186
+ (result, index) => renderResult(
2187
+ result,
2188
+ categorizedResults.titleMatches.length + categorizedResults.headingMatches.length + index
2189
+ )
2190
+ )
2191
+ ] })
2192
+ ] }) : /* @__PURE__ */ jsx("div", { className: styles$6.category, children: results.map((result, index) => renderResult(result, index)) }) })
2193
+ ] });
2194
+ };
2195
+
2196
+ const container$4 = "_container_1dw3c_2";
2197
+ const containerCompact = "_containerCompact_1dw3c_7";
2198
+ const input = "_input_1dw3c_13";
2199
+ const inputCompact = "_inputCompact_1dw3c_37";
2200
+ const styles$5 = {
2201
+ container: container$4,
2202
+ containerCompact: containerCompact,
2203
+ input: input,
2204
+ inputCompact: inputCompact
2205
+ };
2206
+
2207
+ const FileSearch = ({
2208
+ query,
2209
+ onChange,
2210
+ placeholder = "Search files...",
2211
+ className,
2212
+ compact = false
2213
+ }) => {
2214
+ const handleChange = (e) => {
2215
+ onChange(e.target.value);
2216
+ };
2217
+ return /* @__PURE__ */ jsx(
2218
+ "div",
2219
+ {
2220
+ className: clsx(
2221
+ styles$5.container,
2222
+ compact && styles$5.containerCompact,
2223
+ className
2224
+ ),
2225
+ "data-papyr-component": "FileSearch",
2226
+ children: /* @__PURE__ */ jsx(
2227
+ "input",
2228
+ {
2229
+ type: "search",
2230
+ className: clsx(
2231
+ styles$5.input,
2232
+ compact && styles$5.inputCompact
2233
+ ),
2234
+ placeholder,
2235
+ value: query,
2236
+ onChange: handleChange,
2237
+ autoComplete: "off",
2238
+ "aria-label": "Search files"
2239
+ }
2240
+ )
2241
+ }
2242
+ );
2243
+ };
2244
+
2245
+ const fileHierarchy = "_fileHierarchy_15xp0_1";
2246
+ const header = "_header_15xp0_11";
2247
+ const sectionTitle = "_sectionTitle_15xp0_17";
2248
+ const searchContainer = "_searchContainer_15xp0_26";
2249
+ const scrollContainer = "_scrollContainer_15xp0_33";
2250
+ const tree = "_tree_15xp0_48";
2251
+ const treeItem = "_treeItem_15xp0_54";
2252
+ const folderHeader = "_folderHeader_15xp0_60";
2253
+ const folderHeaderClickable = "_folderHeaderClickable_15xp0_70";
2254
+ const folderName = "_folderName_15xp0_74";
2255
+ const chevron = "_chevron_15xp0_78";
2256
+ const chevronExpanded = "_chevronExpanded_15xp0_109";
2257
+ const children = "_children_15xp0_124";
2258
+ const noteItem = "_noteItem_15xp0_134";
2259
+ const noteButton = "_noteButton_15xp0_138";
2260
+ const noteLabel = "_noteLabel_15xp0_157";
2261
+ const noteButtonActive = "_noteButtonActive_15xp0_180";
2262
+ const noteButtonSearchMatch = "_noteButtonSearchMatch_15xp0_189";
2263
+ const emptyState = "_emptyState_15xp0_198";
2264
+ const styles$4 = {
2265
+ fileHierarchy: fileHierarchy,
2266
+ header: header,
2267
+ sectionTitle: sectionTitle,
2268
+ searchContainer: searchContainer,
2269
+ scrollContainer: scrollContainer,
2270
+ tree: tree,
2271
+ treeItem: treeItem,
2272
+ folderHeader: folderHeader,
2273
+ folderHeaderClickable: folderHeaderClickable,
2274
+ folderName: folderName,
2275
+ chevron: chevron,
2276
+ chevronExpanded: chevronExpanded,
2277
+ children: children,
2278
+ noteItem: noteItem,
2279
+ noteButton: noteButton,
2280
+ noteLabel: noteLabel,
2281
+ noteButtonActive: noteButtonActive,
2282
+ noteButtonSearchMatch: noteButtonSearchMatch,
2283
+ emptyState: emptyState
2284
+ };
2285
+
2286
+ const capitalizeFirstLetter = (value) => {
2287
+ if (!value) return value;
2288
+ return value.charAt(0).toUpperCase() + value.slice(1);
2289
+ };
2290
+ const formatFolderName = (name) => capitalizeFirstLetter(name);
2291
+ const getNoteDisplayText = (slug, noteTitleMap) => {
2292
+ const title = noteTitleMap?.[slug] ?? slug;
2293
+ return capitalizeFirstLetter(title);
2294
+ };
2295
+
2296
+ const containsSlug = (node, slug) => {
2297
+ if (!slug) return false;
2298
+ if (node.notes.includes(slug)) return true;
2299
+ return node.children.some((child) => containsSlug(child, slug));
2300
+ };
2301
+ const MemoizedFolderTree = memo(({
2302
+ node,
2303
+ onNoteClick,
2304
+ renderNoteLink,
2305
+ activeSlug,
2306
+ noteTitleMap,
2307
+ depth = 0,
2308
+ isSearching = false,
2309
+ searchQuery = ""
2310
+ }) => {
2311
+ const [isExpanded, setIsExpanded] = useState(true);
2312
+ const hasChildren = node.children.length > 0 || node.notes.length > 0;
2313
+ const toggleExpanded = () => {
2314
+ if (hasChildren) {
2315
+ setIsExpanded(!isExpanded);
2316
+ }
2317
+ };
2318
+ const hasActiveDescendant = useMemo(() => containsSlug(node, activeSlug), [node, activeSlug]);
2319
+ useEffect(() => {
2320
+ if (hasActiveDescendant) {
2321
+ setIsExpanded(true);
2322
+ }
2323
+ }, [hasActiveDescendant]);
2324
+ const isOpen = isExpanded || isSearching;
2325
+ const normalizedSearchQuery = searchQuery.trim().toLowerCase();
2326
+ return /* @__PURE__ */ jsxs("li", { className: styles$4.treeItem, children: [
2327
+ /* @__PURE__ */ jsxs(
2328
+ "div",
2329
+ {
2330
+ className: clsx(
2331
+ styles$4.folderHeader,
2332
+ hasChildren && styles$4.folderHeaderClickable
2333
+ ),
2334
+ onClick: toggleExpanded,
2335
+ role: hasChildren ? "button" : void 0,
2336
+ "aria-expanded": hasChildren ? isOpen ? true : false : void 0,
2337
+ children: [
2338
+ hasChildren && /* @__PURE__ */ jsx("span", { className: clsx(styles$4.chevron, isOpen && styles$4.chevronExpanded), children: "›" }),
2339
+ /* @__PURE__ */ jsx("span", { className: styles$4.folderName, children: formatFolderName(node.name) })
2340
+ ]
2341
+ }
2342
+ ),
2343
+ isOpen && hasChildren && /* @__PURE__ */ jsxs("ul", { className: styles$4.children, children: [
2344
+ node.children.map((child) => /* @__PURE__ */ jsx(
2345
+ MemoizedFolderTree,
2346
+ {
2347
+ node: child,
2348
+ onNoteClick,
2349
+ renderNoteLink,
2350
+ activeSlug,
2351
+ noteTitleMap,
2352
+ depth: depth + 1,
2353
+ isSearching,
2354
+ searchQuery
2355
+ },
2356
+ child.path
2357
+ )),
2358
+ node.notes.map((slug) => {
2359
+ const displayText = getNoteDisplayText(slug, noteTitleMap);
2360
+ const isSearchMatch = Boolean(normalizedSearchQuery) && displayText.toLowerCase().includes(normalizedSearchQuery);
2361
+ const isActive = slug === activeSlug;
2362
+ const handleClick = () => onNoteClick?.(slug);
2363
+ const renderContext = {
2364
+ onClick: handleClick,
2365
+ isActive,
2366
+ isSearchMatch
2367
+ };
2368
+ return /* @__PURE__ */ jsx("li", { className: styles$4.noteItem, children: renderNoteLink ? renderNoteLink(slug, renderContext) : /* @__PURE__ */ jsx(
2369
+ MemoizedNoteButton,
2370
+ {
2371
+ displayText,
2372
+ isActive,
2373
+ isSearchMatch,
2374
+ onClick: handleClick
2375
+ }
2376
+ ) }, slug);
2377
+ })
2378
+ ] })
2379
+ ] });
2380
+ });
2381
+ const MemoizedNoteButton = memo(({ displayText, isActive, isSearchMatch, onClick }) => {
2382
+ return /* @__PURE__ */ jsx(
2383
+ "button",
2384
+ {
2385
+ type: "button",
2386
+ className: clsx(
2387
+ styles$4.noteButton,
2388
+ isActive && styles$4.noteButtonActive,
2389
+ isSearchMatch && styles$4.noteButtonSearchMatch
2390
+ ),
2391
+ onClick,
2392
+ children: /* @__PURE__ */ jsx("span", { className: styles$4.noteLabel, children: displayText })
2393
+ }
2394
+ );
2395
+ });
2396
+ MemoizedFolderTree.displayName = "MemoizedFolderTree";
2397
+ MemoizedNoteButton.displayName = "MemoizedNoteButton";
2398
+
2399
+ const normalizeTree = (tree) => {
2400
+ if (Array.isArray(tree)) {
2401
+ return { nodes: tree, rootNotes: [] };
2402
+ }
2403
+ return {
2404
+ nodes: tree.children,
2405
+ rootNotes: [...tree.notes]
2406
+ };
2407
+ };
2408
+ const filterTreeBySearch = (tree, searchQuery, noteTitleMap) => {
2409
+ if (!searchQuery.trim()) return tree;
2410
+ const query = searchQuery.toLowerCase();
2411
+ return tree.map((node) => {
2412
+ const filteredNotes = node.notes.filter((slug) => {
2413
+ const displayText = getNoteDisplayText(slug, noteTitleMap);
2414
+ return displayText.toLowerCase().includes(query);
2415
+ });
2416
+ const filteredChildren = filterTreeBySearch(node.children, searchQuery, noteTitleMap);
2417
+ const hasMatches = filteredNotes.length > 0 || filteredChildren.length > 0;
2418
+ if (hasMatches) {
2419
+ return {
2420
+ ...node,
2421
+ notes: filteredNotes,
2422
+ children: filteredChildren
2423
+ };
2424
+ }
2425
+ return null;
2426
+ }).filter(Boolean);
2427
+ };
2428
+ const FileHierarchy = ({
2429
+ tree,
2430
+ className,
2431
+ onNoteClick,
2432
+ renderNoteLink,
2433
+ activeSlug,
2434
+ notes,
2435
+ searchable = false,
2436
+ searchIndex,
2437
+ searchPlaceholder = "Search files..."
2438
+ }) => {
2439
+ const [fallbackSearchQuery, setFallbackSearchQuery] = useState("");
2440
+ const noteTitleMap = useMemo(() => {
2441
+ if (!notes) return void 0;
2442
+ return notes.reduce((acc, note) => {
2443
+ acc[note.slug] = note.title;
2444
+ return acc;
2445
+ }, {});
2446
+ }, [notes]);
2447
+ const isFallbackSearchEnabled = searchable && !searchIndex;
2448
+ const isFallbackSearching = isFallbackSearchEnabled && Boolean(fallbackSearchQuery.trim());
2449
+ useEffect(() => {
2450
+ if (!isFallbackSearchEnabled) return;
2451
+ console.warn("[papyr] FileHierarchy rendered with `searchable` but no `searchIndex`. Falling back to tree-based filtering.");
2452
+ }, [isFallbackSearchEnabled]);
2453
+ useEffect(() => {
2454
+ if (searchIndex) {
2455
+ setFallbackSearchQuery("");
2456
+ }
2457
+ }, [searchIndex]);
2458
+ const handleSearchResultClick = useCallback((slug) => {
2459
+ onNoteClick?.(slug);
2460
+ }, [onNoteClick]);
2461
+ const normalizedTree = useMemo(() => normalizeTree(tree), [tree]);
2462
+ const filteredHierarchy = useMemo(() => {
2463
+ if (!isFallbackSearchEnabled) {
2464
+ return normalizedTree;
2465
+ }
2466
+ const filteredNodes = filterTreeBySearch(
2467
+ normalizedTree.nodes,
2468
+ fallbackSearchQuery,
2469
+ noteTitleMap
2470
+ );
2471
+ const query = fallbackSearchQuery.trim().toLowerCase();
2472
+ const filteredRootNotes = normalizedTree.rootNotes.filter((slug) => {
2473
+ if (!query) return true;
2474
+ const displayText = getNoteDisplayText(slug, noteTitleMap);
2475
+ return displayText.toLowerCase().includes(query);
2476
+ });
2477
+ return {
2478
+ nodes: filteredNodes,
2479
+ rootNotes: filteredRootNotes
2480
+ };
2481
+ }, [normalizedTree, isFallbackSearchEnabled, fallbackSearchQuery, noteTitleMap]);
2482
+ const treeToRender = filteredHierarchy.nodes;
2483
+ const rootNotesToRender = filteredHierarchy.rootNotes;
2484
+ const effectiveSearchQuery = isFallbackSearchEnabled ? fallbackSearchQuery : "";
2485
+ const isEmptyState = !treeToRender.length && !rootNotesToRender.length;
2486
+ const emptyStateMessage = isFallbackSearching ? "No results found" : "No files to display yet";
2487
+ return /* @__PURE__ */ jsxs("aside", { className: clsx(styles$4.fileHierarchy, className), "data-papyr-component": "FileHierarchy", "aria-label": "File navigation", children: [
2488
+ /* @__PURE__ */ jsx("div", { className: styles$4.header, children: /* @__PURE__ */ jsx("span", { className: styles$4.sectionTitle, children: "Files" }) }),
2489
+ searchable && /* @__PURE__ */ jsx("div", { className: styles$4.searchContainer, children: searchIndex ? /* @__PURE__ */ jsx(
2490
+ SearchDropdown,
2491
+ {
2492
+ searchIndex,
2493
+ onResultClick: handleSearchResultClick,
2494
+ placeholder: searchPlaceholder
2495
+ }
2496
+ ) : /* @__PURE__ */ jsx(
2497
+ FileSearch,
2498
+ {
2499
+ query: fallbackSearchQuery,
2500
+ onChange: setFallbackSearchQuery,
2501
+ placeholder: searchPlaceholder,
2502
+ compact: true
2503
+ }
2504
+ ) }),
2505
+ /* @__PURE__ */ jsx("div", { className: styles$4.scrollContainer, children: isEmptyState ? /* @__PURE__ */ jsx("div", { className: styles$4.emptyState, children: emptyStateMessage }) : /* @__PURE__ */ jsxs("ul", { className: styles$4.tree, children: [
2506
+ rootNotesToRender.map((slug) => {
2507
+ const displayText = getNoteDisplayText(slug, noteTitleMap);
2508
+ const isActive = slug === activeSlug;
2509
+ const isSearchMatch = effectiveSearchQuery.trim().length > 0 && displayText.toLowerCase().includes(effectiveSearchQuery.toLowerCase());
2510
+ const handleClick = () => onNoteClick?.(slug);
2511
+ const renderContext = {
2512
+ onClick: handleClick,
2513
+ isActive,
2514
+ isSearchMatch
2515
+ };
2516
+ return /* @__PURE__ */ jsx("li", { className: styles$4.noteItem, children: renderNoteLink ? renderNoteLink(slug, renderContext) : /* @__PURE__ */ jsx(
2517
+ "button",
2518
+ {
2519
+ type: "button",
2520
+ className: clsx(
2521
+ styles$4.noteButton,
2522
+ isActive && styles$4.noteButtonActive,
2523
+ isSearchMatch && styles$4.noteButtonSearchMatch
2524
+ ),
2525
+ onClick: handleClick,
2526
+ children: displayText
2527
+ }
2528
+ ) }, `root-${slug}`);
2529
+ }),
2530
+ treeToRender.map((node) => /* @__PURE__ */ jsx(
2531
+ MemoizedFolderTree,
2532
+ {
2533
+ node,
2534
+ onNoteClick,
2535
+ renderNoteLink,
2536
+ activeSlug,
2537
+ noteTitleMap,
2538
+ isSearching: isFallbackSearching,
2539
+ searchQuery: effectiveSearchQuery
2540
+ },
2541
+ node.path
2542
+ ))
2543
+ ] }) })
2544
+ ] });
2545
+ };
2546
+
2547
+ const container$3 = "_container_f89dg_1";
2548
+ const icon = "_icon_f89dg_11";
2549
+ const emoji = "_emoji_f89dg_16";
2550
+ const title = "_title_f89dg_21";
2551
+ const description = "_description_f89dg_28";
2552
+ const actions = "_actions_f89dg_36";
2553
+ const styles$3 = {
2554
+ container: container$3,
2555
+ icon: icon,
2556
+ emoji: emoji,
2557
+ title: title,
2558
+ description: description,
2559
+ actions: actions
2560
+ };
2561
+
2562
+ const EmptyState = ({
2563
+ icon,
2564
+ title,
2565
+ description,
2566
+ children,
2567
+ className
2568
+ }) => {
2569
+ return /* @__PURE__ */ jsxs(
2570
+ "div",
2571
+ {
2572
+ className: clsx(styles$3.container, className),
2573
+ "data-papyr-component": "EmptyState",
2574
+ role: "status",
2575
+ "aria-label": title,
2576
+ children: [
2577
+ icon && /* @__PURE__ */ jsx("div", { className: styles$3.icon, "aria-hidden": "true", children: typeof icon === "string" ? /* @__PURE__ */ jsx("span", { className: styles$3.emoji, children: icon }) : icon }),
2578
+ /* @__PURE__ */ jsx("h2", { className: styles$3.title, children: title }),
2579
+ description && /* @__PURE__ */ jsx("p", { className: styles$3.description, children: description }),
2580
+ children && /* @__PURE__ */ jsx("div", { className: styles$3.actions, children })
2581
+ ]
2582
+ }
2583
+ );
2584
+ };
2585
+
2586
+ const container$2 = "_container_13ske_1";
2587
+ const skipLink$1 = "_skipLink_13ske_9";
2588
+ const containerFullHeight$1 = "_containerFullHeight_13ske_26";
2589
+ const containerAutoHeight$1 = "_containerAutoHeight_13ske_32";
2590
+ const sidebar$1 = "_sidebar_13ske_36";
2591
+ const sidebarScrollArea$1 = "_sidebarScrollArea_13ske_46";
2592
+ const sidebarScrollAreaFullHeight = "_sidebarScrollAreaFullHeight_13ske_52";
2593
+ const sidebarScrollAreaAutoHeight = "_sidebarScrollAreaAutoHeight_13ske_60";
2594
+ const sidebarScrollAreaHiddenScrollbars$1 = "_sidebarScrollAreaHiddenScrollbars_13ske_64";
2595
+ const main$1 = "_main_13ske_74";
2596
+ const mainScrollArea$1 = "_mainScrollArea_13ske_85";
2597
+ const mainScrollAreaFullHeight = "_mainScrollAreaFullHeight_13ske_94";
2598
+ const mainScrollAreaAutoHeight = "_mainScrollAreaAutoHeight_13ske_102";
2599
+ const styles$2 = {
2600
+ container: container$2,
2601
+ skipLink: skipLink$1,
2602
+ containerFullHeight: containerFullHeight$1,
2603
+ containerAutoHeight: containerAutoHeight$1,
2604
+ sidebar: sidebar$1,
2605
+ sidebarScrollArea: sidebarScrollArea$1,
2606
+ sidebarScrollAreaFullHeight: sidebarScrollAreaFullHeight,
2607
+ sidebarScrollAreaAutoHeight: sidebarScrollAreaAutoHeight,
2608
+ sidebarScrollAreaHiddenScrollbars: sidebarScrollAreaHiddenScrollbars$1,
2609
+ main: main$1,
2610
+ mainScrollArea: mainScrollArea$1,
2611
+ mainScrollAreaFullHeight: mainScrollAreaFullHeight,
2612
+ mainScrollAreaAutoHeight: mainScrollAreaAutoHeight
2613
+ };
2614
+
2615
+ const SidebarLayout = ({
2616
+ sidebar,
2617
+ main,
2618
+ className,
2619
+ sidebarWidth = 280,
2620
+ fullHeight = true
2621
+ }) => {
2622
+ const gridTemplateColumns = typeof sidebarWidth === "number" ? `${sidebarWidth}px 1fr` : `${sidebarWidth} 1fr`;
2623
+ const containerClassName = clsx(
2624
+ styles$2.container,
2625
+ fullHeight ? styles$2.containerFullHeight : styles$2.containerAutoHeight,
2626
+ className
2627
+ );
2628
+ const sidebarScrollClassName = clsx(
2629
+ styles$2.sidebarScrollArea,
2630
+ fullHeight ? styles$2.sidebarScrollAreaFullHeight : styles$2.sidebarScrollAreaAutoHeight,
2631
+ styles$2.sidebarScrollAreaHiddenScrollbars
2632
+ );
2633
+ const mainScrollClassName = clsx(
2634
+ styles$2.mainScrollArea,
2635
+ fullHeight ? styles$2.mainScrollAreaFullHeight : styles$2.mainScrollAreaAutoHeight
2636
+ );
2637
+ return /* @__PURE__ */ jsxs(
2638
+ "div",
2639
+ {
2640
+ className: containerClassName,
2641
+ style: { gridTemplateColumns },
2642
+ "data-papyr-component": "SidebarLayout",
2643
+ children: [
2644
+ /* @__PURE__ */ jsx("a", { href: "#main-content", className: styles$2.skipLink, children: "Skip to main content" }),
2645
+ /* @__PURE__ */ jsx("aside", { className: styles$2.sidebar, "aria-label": "Primary navigation", children: /* @__PURE__ */ jsx("div", { className: sidebarScrollClassName, children: sidebar }) }),
2646
+ /* @__PURE__ */ jsx("main", { id: "main-content", className: styles$2.main, children: /* @__PURE__ */ jsx("div", { className: mainScrollClassName, children: main }) })
2647
+ ]
2648
+ }
2649
+ );
2650
+ };
2651
+
2652
+ const container$1 = "_container_1ttbz_1";
2653
+ const skipLink = "_skipLink_1ttbz_9";
2654
+ const containerFullHeight = "_containerFullHeight_1ttbz_26";
2655
+ const containerAutoHeight = "_containerAutoHeight_1ttbz_32";
2656
+ const sidebar = "_sidebar_1ttbz_36";
2657
+ const sidebarScrollArea = "_sidebarScrollArea_1ttbz_51";
2658
+ const sidebarScrollAreaHiddenScrollbars = "_sidebarScrollAreaHiddenScrollbars_1ttbz_62";
2659
+ const main = "_main_1ttbz_72";
2660
+ const mainScrollArea = "_mainScrollArea_1ttbz_84";
2661
+ const styles$1 = {
2662
+ container: container$1,
2663
+ skipLink: skipLink,
2664
+ containerFullHeight: containerFullHeight,
2665
+ containerAutoHeight: containerAutoHeight,
2666
+ sidebar: sidebar,
2667
+ sidebarScrollArea: sidebarScrollArea,
2668
+ sidebarScrollAreaHiddenScrollbars: sidebarScrollAreaHiddenScrollbars,
2669
+ main: main,
2670
+ mainScrollArea: mainScrollArea
2671
+ };
2672
+
2673
+ const toCssSize = (value) => typeof value === "number" ? `${value}px` : value;
2674
+ const DoubleSidebarLayout = ({
2675
+ leftSidebar,
2676
+ main,
2677
+ rightSidebar = null,
2678
+ leftWidth = 280,
2679
+ rightWidth = 480,
2680
+ fullHeight = true,
2681
+ hideSidebarScrollbars = true,
2682
+ className
2683
+ }) => {
2684
+ const containerClassName = clsx(
2685
+ styles$1.container,
2686
+ fullHeight ? styles$1.containerFullHeight : styles$1.containerAutoHeight,
2687
+ className
2688
+ );
2689
+ const sidebarScrollClassName = clsx(
2690
+ styles$1.sidebarScrollArea,
2691
+ hideSidebarScrollbars && styles$1.sidebarScrollAreaHiddenScrollbars
2692
+ );
2693
+ const leftStyle = { width: toCssSize(leftWidth) };
2694
+ const rightStyle = { width: toCssSize(rightWidth) };
2695
+ return /* @__PURE__ */ jsxs("div", { className: containerClassName, "data-papyr-component": "DoubleSidebarLayout", children: [
2696
+ /* @__PURE__ */ jsx("a", { href: "#main-content", className: styles$1.skipLink, children: "Skip to main content" }),
2697
+ /* @__PURE__ */ jsx("aside", { className: styles$1.sidebar, style: leftStyle, "aria-label": "File navigation", children: /* @__PURE__ */ jsx("div", { className: sidebarScrollClassName, children: leftSidebar }) }),
2698
+ /* @__PURE__ */ jsx("main", { id: "main-content", className: styles$1.main, children: /* @__PURE__ */ jsx("div", { className: styles$1.mainScrollArea, children: main }) }),
2699
+ rightSidebar ? /* @__PURE__ */ jsx("aside", { className: styles$1.sidebar, style: rightStyle, "aria-label": "Contextual sidebar", children: /* @__PURE__ */ jsx("div", { className: sidebarScrollClassName, children: rightSidebar }) }) : null
2700
+ ] });
2701
+ };
2702
+
2703
+ const container = "_container_1uvfd_1";
2704
+ const list = "_list_1uvfd_13";
2705
+ const item = "_item_1uvfd_40";
2706
+ const itemActive = "_itemActive_1uvfd_48";
2707
+ const link = "_link_1uvfd_65";
2708
+ const linkActive = "_linkActive_1uvfd_84";
2709
+ const level1 = "_level1_1uvfd_91";
2710
+ const level2 = "_level2_1uvfd_102";
2711
+ const level3 = "_level3_1uvfd_106";
2712
+ const level4 = "_level4_1uvfd_110";
2713
+ const level5 = "_level5_1uvfd_114";
2714
+ const level6 = "_level6_1uvfd_118";
2715
+ const styles = {
2716
+ container: container,
2717
+ list: list,
2718
+ item: item,
2719
+ itemActive: itemActive,
2720
+ link: link,
2721
+ linkActive: linkActive,
2722
+ level1: level1,
2723
+ level2: level2,
2724
+ level3: level3,
2725
+ level4: level4,
2726
+ level5: level5,
2727
+ level6: level6
2728
+ };
2729
+
2730
+ const TableOfContents = ({
2731
+ note,
2732
+ className,
2733
+ title = "On this page",
2734
+ onNavigate
2735
+ }) => {
2736
+ const headings = note?.headings ?? [];
2737
+ const hasRenderableHeadings = Boolean(note && headings.length > 0);
2738
+ const [visibleHeadings, setVisibleHeadings] = useState(() => /* @__PURE__ */ new Set());
2739
+ const clearVisibleHeadings = useCallback(() => {
2740
+ setVisibleHeadings((prev) => {
2741
+ if (prev.size === 0) {
2742
+ return prev;
2743
+ }
2744
+ return /* @__PURE__ */ new Set();
2745
+ });
2746
+ }, []);
2747
+ const headingSignature = headings.map((heading) => heading.slug).join("|");
2748
+ useEffect(() => {
2749
+ if (typeof window === "undefined") {
2750
+ return;
2751
+ }
2752
+ if (!hasRenderableHeadings) {
2753
+ clearVisibleHeadings();
2754
+ return;
2755
+ }
2756
+ const targets = headings.map((heading) => {
2757
+ const element = document.getElementById(heading.slug);
2758
+ return element ? { slug: heading.slug, element } : null;
2759
+ }).filter((value) => Boolean(value));
2760
+ if (!targets.length) {
2761
+ clearVisibleHeadings();
2762
+ return;
2763
+ }
2764
+ const getScrollableParent = (element) => {
2765
+ let parent = element.parentElement;
2766
+ while (parent && parent !== document.body) {
2767
+ const style = window.getComputedStyle(parent);
2768
+ const overflowY = style.overflowY;
2769
+ const overflow = style.overflow;
2770
+ const isScrollable = /(auto|scroll)/.test(`${overflowY}${overflow}`);
2771
+ if (isScrollable && parent.scrollHeight > parent.clientHeight + 1) {
2772
+ return parent;
2773
+ }
2774
+ parent = parent.parentElement;
2775
+ }
2776
+ return window;
2777
+ };
2778
+ const getAbsoluteTop = (element) => element.getBoundingClientRect().top + window.scrollY;
2779
+ const computeVisibleSections = () => {
2780
+ const viewportTop = window.scrollY;
2781
+ const viewportBottom = viewportTop + window.innerHeight;
2782
+ const nextVisible = /* @__PURE__ */ new Set();
2783
+ targets.forEach((target, index) => {
2784
+ const sectionStart = getAbsoluteTop(target.element);
2785
+ const nextTarget = targets[index + 1];
2786
+ const sectionEnd = nextTarget ? getAbsoluteTop(nextTarget.element) : Number.POSITIVE_INFINITY;
2787
+ if (sectionStart < viewportBottom && sectionEnd > viewportTop) {
2788
+ nextVisible.add(target.slug);
2789
+ }
2790
+ });
2791
+ setVisibleHeadings((prev) => {
2792
+ if (prev.size === nextVisible.size) {
2793
+ let identical = true;
2794
+ prev.forEach((slug) => {
2795
+ if (!nextVisible.has(slug)) {
2796
+ identical = false;
2797
+ }
2798
+ });
2799
+ if (identical) {
2800
+ return prev;
2801
+ }
2802
+ }
2803
+ return nextVisible;
2804
+ });
2805
+ };
2806
+ let rafId = null;
2807
+ const scheduleUpdate = () => {
2808
+ if (rafId !== null) {
2809
+ return;
2810
+ }
2811
+ rafId = window.requestAnimationFrame(() => {
2812
+ rafId = null;
2813
+ computeVisibleSections();
2814
+ });
2815
+ };
2816
+ scheduleUpdate();
2817
+ const scrollContainers = Array.from(
2818
+ new Set(targets.map(({ element }) => getScrollableParent(element)))
2819
+ );
2820
+ scrollContainers.forEach((container) => {
2821
+ container.addEventListener("scroll", scheduleUpdate, { passive: true });
2822
+ });
2823
+ window.addEventListener("resize", scheduleUpdate);
2824
+ return () => {
2825
+ if (rafId !== null) {
2826
+ window.cancelAnimationFrame(rafId);
2827
+ }
2828
+ scrollContainers.forEach((container) => {
2829
+ container.removeEventListener("scroll", scheduleUpdate);
2830
+ });
2831
+ window.removeEventListener("resize", scheduleUpdate);
2832
+ };
2833
+ }, [clearVisibleHeadings, hasRenderableHeadings, headingSignature, headings]);
2834
+ const handleClick = (heading, event) => {
2835
+ event.preventDefault();
2836
+ setVisibleHeadings((prev) => {
2837
+ const next = new Set(prev);
2838
+ next.add(heading.slug);
2839
+ return next;
2840
+ });
2841
+ if (onNavigate) {
2842
+ onNavigate(heading);
2843
+ } else if (heading.slug && typeof window !== "undefined") {
2844
+ const element = document.getElementById(heading.slug);
2845
+ if (element) {
2846
+ element.scrollIntoView({ behavior: "smooth", block: "start" });
2847
+ }
2848
+ updateHashWithAnchor(heading.slug);
2849
+ }
2850
+ };
2851
+ if (!hasRenderableHeadings || !note) {
2852
+ return null;
2853
+ }
2854
+ return /* @__PURE__ */ jsx(
2855
+ "nav",
2856
+ {
2857
+ className: clsx(styles.container, className),
2858
+ "aria-label": title ?? "Table of contents",
2859
+ "data-papyr-component": "TableOfContents",
2860
+ children: /* @__PURE__ */ jsx("ul", { className: styles.list, children: headings.map((heading) => /* @__PURE__ */ jsx(
2861
+ "li",
2862
+ {
2863
+ className: clsx(
2864
+ styles.item,
2865
+ styles[`level${heading.level}`],
2866
+ visibleHeadings.has(heading.slug) && styles.itemActive
2867
+ ),
2868
+ children: /* @__PURE__ */ jsx(
2869
+ "a",
2870
+ {
2871
+ href: `#${heading.slug}`,
2872
+ className: clsx(styles.link, visibleHeadings.has(heading.slug) && styles.linkActive),
2873
+ onClick: (event) => handleClick(heading, event),
2874
+ "aria-current": visibleHeadings.has(heading.slug) ? "location" : void 0,
2875
+ children: heading.text
2876
+ }
2877
+ )
2878
+ },
2879
+ heading.slug
2880
+ )) })
2881
+ }
2882
+ );
2883
+ };
2884
+
2885
+ function useActiveNote(notes, initialSlug) {
2886
+ const [activeSlug, setActiveSlugState] = useState(initialSlug ?? null);
2887
+ const hasUserSelection = useRef(false);
2888
+ const prevInitialSlugRef = useRef(initialSlug);
2889
+ const { noteSetSignature, noteSlugSet } = useMemo(() => {
2890
+ const slugs = notes.map((note) => note.slug);
2891
+ const slugSet = new Set(slugs);
2892
+ const signature = [...slugSet].sort().join("\0");
2893
+ return { noteSetSignature: signature, noteSlugSet: slugSet };
2894
+ }, [notes]);
2895
+ const prevSignatureRef = useRef(noteSetSignature);
2896
+ useEffect(() => {
2897
+ const datasetChanged = prevSignatureRef.current !== noteSetSignature;
2898
+ const activeSlugMissing = activeSlug != null && !noteSlugSet.has(activeSlug);
2899
+ prevSignatureRef.current = noteSetSignature;
2900
+ if (datasetChanged || activeSlugMissing) {
2901
+ hasUserSelection.current = false;
2902
+ }
2903
+ if (activeSlugMissing) {
2904
+ setActiveSlugState(null);
2905
+ }
2906
+ }, [noteSetSignature, noteSlugSet, activeSlug]);
2907
+ useEffect(() => {
2908
+ const normalizedSlug = initialSlug ?? null;
2909
+ const prevSlug = prevInitialSlugRef.current ?? null;
2910
+ const initialSlugChanged = normalizedSlug !== prevSlug;
2911
+ prevInitialSlugRef.current = initialSlug;
2912
+ if (!hasUserSelection.current || initialSlugChanged) {
2913
+ if (normalizedSlug !== activeSlug) {
2914
+ setActiveSlugState(normalizedSlug);
2915
+ }
2916
+ }
2917
+ }, [initialSlug, activeSlug]);
2918
+ const activeNote = useMemo(() => {
2919
+ if (!activeSlug) return null;
2920
+ return notes.find((note) => note.slug === activeSlug) ?? null;
2921
+ }, [notes, activeSlug]);
2922
+ const setActiveNote = useCallback((note) => {
2923
+ hasUserSelection.current = true;
2924
+ setActiveSlugState(note?.slug ?? null);
2925
+ }, []);
2926
+ const setActiveSlug = useCallback((slug) => {
2927
+ hasUserSelection.current = true;
2928
+ setActiveSlugState(slug);
2929
+ }, []);
2930
+ return {
2931
+ activeSlug,
2932
+ activeNote,
2933
+ setActiveSlug,
2934
+ setActiveNote
2935
+ };
2936
+ }
2937
+
2938
+ function useRoutableActiveNote(notes, options) {
2939
+ const { getCurrentSlug, onSlugChange } = options;
2940
+ const papyr = useOptionalPapyr();
2941
+ const showToast = papyr?.showToast;
2942
+ const currentRouteSlug = getCurrentSlug();
2943
+ const currentRouteExists = currentRouteSlug !== null && notes.some((note) => note.slug === currentRouteSlug);
2944
+ const routeSlugRef = useRef(currentRouteSlug ?? null);
2945
+ const onSlugChangeRef = useRef(onSlugChange);
2946
+ const syncingRef = useRef(false);
2947
+ const lastMissingSlugRef = useRef(null);
2948
+ const lastValidSlugRef = useRef(currentRouteExists ? currentRouteSlug : null);
2949
+ useEffect(() => {
2950
+ onSlugChangeRef.current = onSlugChange;
2951
+ }, [onSlugChange]);
2952
+ const {
2953
+ activeSlug,
2954
+ activeNote,
2955
+ setActiveSlug: baseSetActiveSlug,
2956
+ setActiveNote: baseSetActiveNote
2957
+ } = useActiveNote(notes, currentRouteSlug ?? void 0);
2958
+ const notifyRouter = useCallback(
2959
+ (slug) => {
2960
+ routeSlugRef.current = slug ?? null;
2961
+ if (!onSlugChangeRef.current) {
2962
+ return;
2963
+ }
2964
+ const currentRouteSlug2 = getCurrentSlug();
2965
+ if (slug !== currentRouteSlug2) {
2966
+ onSlugChangeRef.current(slug);
2967
+ }
2968
+ },
2969
+ [getCurrentSlug]
2970
+ );
2971
+ useEffect(() => {
2972
+ const routeSlug = getCurrentSlug();
2973
+ routeSlugRef.current = routeSlug ?? null;
2974
+ if (!routeSlug) {
2975
+ lastMissingSlugRef.current = null;
2976
+ lastValidSlugRef.current = null;
2977
+ if (activeSlug !== null) {
2978
+ syncingRef.current = true;
2979
+ baseSetActiveSlug(null);
2980
+ syncingRef.current = false;
2981
+ }
2982
+ return;
2983
+ }
2984
+ const hasNotes = notes.length > 0;
2985
+ const exists = hasNotes && notes.some((note) => note.slug === routeSlug);
2986
+ if (exists) {
2987
+ lastMissingSlugRef.current = null;
2988
+ lastValidSlugRef.current = routeSlug;
2989
+ if (routeSlug !== activeSlug) {
2990
+ syncingRef.current = true;
2991
+ baseSetActiveSlug(routeSlug);
2992
+ syncingRef.current = false;
2993
+ }
2994
+ return;
2995
+ }
2996
+ if (!hasNotes) {
2997
+ return;
2998
+ }
2999
+ if (lastMissingSlugRef.current !== routeSlug) {
3000
+ lastMissingSlugRef.current = routeSlug;
3001
+ showToast?.(`Note "${routeSlug}" not found.`, { duration: 3e3 });
3002
+ }
3003
+ if (lastValidSlugRef.current === routeSlug) {
3004
+ lastValidSlugRef.current = null;
3005
+ }
3006
+ const fallbackSlug = lastValidSlugRef.current;
3007
+ if (fallbackSlug !== routeSlug) {
3008
+ notifyRouter(fallbackSlug ?? null);
3009
+ }
3010
+ if (fallbackSlug !== activeSlug) {
3011
+ syncingRef.current = true;
3012
+ baseSetActiveSlug(fallbackSlug ?? null);
3013
+ syncingRef.current = false;
3014
+ }
3015
+ }, [getCurrentSlug, currentRouteSlug, notes, activeSlug, baseSetActiveSlug, notifyRouter, showToast]);
3016
+ const setActiveSlug = useCallback(
3017
+ (slug) => {
3018
+ baseSetActiveSlug(slug);
3019
+ if (!syncingRef.current) {
3020
+ notifyRouter(slug);
3021
+ }
3022
+ },
3023
+ [baseSetActiveSlug, notifyRouter]
3024
+ );
3025
+ const setActiveNote = useCallback(
3026
+ (note) => {
3027
+ baseSetActiveNote(note);
3028
+ if (!syncingRef.current) {
3029
+ notifyRouter(note?.slug ?? null);
3030
+ }
3031
+ },
3032
+ [baseSetActiveNote, notifyRouter]
3033
+ );
3034
+ return {
3035
+ activeSlug,
3036
+ activeNote,
3037
+ setActiveSlug,
3038
+ setActiveNote
3039
+ };
3040
+ }
3041
+
3042
+ const resolveSlot = (slot, context, fallback) => {
3043
+ if (typeof slot === "function") {
3044
+ return slot(context);
3045
+ }
3046
+ if (slot !== void 0) {
3047
+ return slot;
3048
+ }
3049
+ return fallback;
3050
+ };
3051
+ const WorkspaceBlock = ({
3052
+ notes,
3053
+ tree,
3054
+ searchIndex,
3055
+ graph = null,
3056
+ initialSlug = null,
3057
+ activeSlug: controlledActiveSlug,
3058
+ onActiveSlugChange,
3059
+ className,
3060
+ leftWidth,
3061
+ rightWidth,
3062
+ leftSidebar,
3063
+ main,
3064
+ rightSidebar,
3065
+ hideTableOfContents = false,
3066
+ onNoteChange,
3067
+ fileHierarchyProps,
3068
+ tableOfContentsProps,
3069
+ emptyState,
3070
+ hideSidebarScrollbars = true
3071
+ }) => {
3072
+ const isControlled = controlledActiveSlug !== void 0;
3073
+ const internalState = useActiveNote(notes, initialSlug);
3074
+ const activeSlug = isControlled ? controlledActiveSlug : internalState.activeSlug;
3075
+ const activeNote = useMemo(() => {
3076
+ if (isControlled) {
3077
+ if (!controlledActiveSlug) return null;
3078
+ return notes.find((n) => n.slug === controlledActiveSlug) ?? null;
3079
+ }
3080
+ return internalState.activeNote;
3081
+ }, [isControlled, controlledActiveSlug, notes, internalState.activeNote]);
3082
+ const setActiveSlug = useCallback(
3083
+ (slug) => {
3084
+ if (isControlled) {
3085
+ onActiveSlugChange?.(slug);
3086
+ } else {
3087
+ internalState.setActiveSlug(slug);
3088
+ }
3089
+ },
3090
+ [isControlled, onActiveSlugChange, internalState]
3091
+ );
3092
+ const setActiveNote = useCallback(
3093
+ (note) => {
3094
+ if (isControlled) {
3095
+ onActiveSlugChange?.(note?.slug ?? null);
3096
+ } else {
3097
+ internalState.setActiveNote(note);
3098
+ }
3099
+ },
3100
+ [isControlled, onActiveSlugChange, internalState]
3101
+ );
3102
+ useEffect(() => {
3103
+ onNoteChange?.(activeNote);
3104
+ }, [activeNote, onNoteChange]);
3105
+ const context = useMemo(
3106
+ () => ({
3107
+ notes,
3108
+ searchIndex,
3109
+ graph,
3110
+ activeSlug,
3111
+ activeNote,
3112
+ setActiveSlug,
3113
+ setActiveNote
3114
+ }),
3115
+ [notes, searchIndex, graph, activeSlug, activeNote, setActiveSlug, setActiveNote]
3116
+ );
3117
+ const defaultFileHierarchy = /* @__PURE__ */ jsx(
3118
+ FileHierarchy,
3119
+ {
3120
+ tree,
3121
+ onNoteClick: setActiveSlug,
3122
+ activeSlug: activeSlug ?? void 0,
3123
+ notes,
3124
+ searchable: true,
3125
+ searchIndex,
3126
+ ...fileHierarchyProps
3127
+ }
3128
+ );
3129
+ const defaultMain = /* @__PURE__ */ jsx(
3130
+ NoteViewer,
3131
+ {
3132
+ note: activeNote,
3133
+ onNavigateNote: setActiveSlug,
3134
+ emptyState: emptyState ?? /* @__PURE__ */ jsx(
3135
+ EmptyState,
3136
+ {
3137
+ icon: "📝",
3138
+ title: "No note selected",
3139
+ description: "Choose a note from the file hierarchy to start reading."
3140
+ }
3141
+ )
3142
+ }
3143
+ );
3144
+ const defaultRightSidebar = hideTableOfContents ? null : /* @__PURE__ */ jsxs(Fragment, { children: [
3145
+ graph && activeSlug && /* @__PURE__ */ jsx("div", { style: {
3146
+ height: "240px",
3147
+ flexShrink: 0,
3148
+ borderBottom: "1px solid var(--papyr-border, rgba(255, 255, 255, 0.1))"
3149
+ }, children: /* @__PURE__ */ jsx(
3150
+ GraphView,
3151
+ {
3152
+ graph,
3153
+ activeSlug,
3154
+ onNodeSelect: (node) => setActiveSlug(node.id),
3155
+ showFullscreenToggle: false
3156
+ }
3157
+ ) }),
3158
+ /* @__PURE__ */ jsx(TableOfContents, { note: activeNote, ...tableOfContentsProps })
3159
+ ] });
3160
+ const resolvedLeft = resolveSlot(leftSidebar, context, defaultFileHierarchy);
3161
+ const resolvedMain = resolveSlot(main, context, defaultMain);
3162
+ const resolvedRight = resolveSlot(rightSidebar, context, defaultRightSidebar);
3163
+ return /* @__PURE__ */ jsx(PapyrProvider, { notes, searchIndex, graph, children: /* @__PURE__ */ jsx(
3164
+ DoubleSidebarLayout,
3165
+ {
3166
+ className,
3167
+ leftSidebar: resolvedLeft,
3168
+ main: resolvedMain,
3169
+ rightSidebar: resolvedRight ?? void 0,
3170
+ leftWidth,
3171
+ rightWidth,
3172
+ hideSidebarScrollbars
3173
+ }
3174
+ ) });
3175
+ };
3176
+
3177
+ export { Breadcrumbs, DoubleSidebarLayout, EmptyState, FileHierarchy, FileSearch, GraphView, HoverPreview, MiniGraph, NotePreviewContent, NoteViewer, PapyrProvider, SearchBar, SearchDropdown, SidebarLayout, TableOfContents, TagFilter, ToastContainer, WorkspaceBlock, buildWikiLink, findNoteBySlug, hydrateSearchIndex, parseNoteHash, toForceGraphData, updateHashWithAnchor, useActiveNote, useNotes, useOptionalPapyr, usePapyr, useRoutableActiveNote };
3178
+ //# sourceMappingURL=index.js.map