@terra-symposium/docs 0.1.78 → 0.1.80

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.
@@ -10,63 +10,48 @@ IMPORTS
10
10
  //////////////////////////////////////////////////*/
11
11
  // API
12
12
  import { $leafTitle, $mode, $transition, $unsavedDrafts, memoryDrafts, useAtom, } from '../../../../lib/terra/docs/state.js';
13
- import { extractAllNodeIds, getSiblings as getSibs, parseDocsRoute as route, getStableKey, sanitizeName, } from '../../../../lib/terra/docs/tree.js';
14
- import { navigateDocs as navigate } from '../../../../lib/terra/docs/navigation.js';
15
- import { runMutation } from '../../../../lib/terra/docs/mutations.js';
16
- import { getHue } from '@terra-symposium/styling';
17
13
  import { actions } from '../../../../lib/terra/runtime/action-proxy.js';
18
14
  import { archiveDocs } from '../../../../lib/terra/docs/archive.js';
19
- import { ManageAttachmentsDialog } from './_manage-attachments-dialog.js';
20
- import { useRenderTripwire } from './use-render-tripwire.js';
21
- import { bName, buildRoute as buildRoute, cTitle, dirname, docsPath, docsRootPath, FILES, toNodeLabel, toPaths, } from '../../../../lib/terra/runtime/shared.js';
22
- import { cn, createTrace, notify } from '../../../../lib/terra/runtime/runtime.js';
23
- import { isDevOrigin } from '../../../../lib/terra/runtime/runtime.js';
15
+ import { bName, buildRoute as buildRoute, cTitle, dirname, docsPath, docsRootPath, toNodeLabel, toPaths, } from '../../../../lib/terra/runtime/shared.js';
16
+ import { cn, createTrace, isDevOrigin, notify } from '../../../../lib/terra/runtime/runtime.js';
24
17
  import { components as repoComponents } from '../../../../lib/terra/custom-components.js';
25
18
  import { currLeafSrc } from '../../../../lib/terra/runtime/leaf.js';
19
+ import { extractAllNodeIds, getSiblings as getSibs, getStableKey, parseDocsRoute as route, sanitizeName, } from '../../../../lib/terra/docs/tree.js';
20
+ import { getHue } from '@terra-symposium/styling';
21
+ import { navigateDocs as navigate } from '../../../../lib/terra/docs/navigation.js';
22
+ import { runMutation } from '../../../../lib/terra/docs/mutations.js';
26
23
  import { toast } from 'sonner';
27
24
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
25
+ import { useRenderTripwire } from './use-render-tripwire.js';
28
26
  // COMPONENTS
29
27
  import * as TerraQualityRegex from '../../../../lib/terra/regex.mjs';
30
28
  import React from 'react';
31
29
  import Tree, { Menu } from './_tree.js';
32
- import User from '../../ui.js';
30
+ import User, { ErrorBoundary } from '../../ui.js';
33
31
  import { Add as AddIcon, ArrowUpRight as PopOutIcon, Box as BoxIcon, ColorPalette as ColorIcon, Download as DownloadIcon, Edit as EditIcon, Link as LinkIcon, TrashCan as TrashIcon, } from '@carbon/icons-react';
34
- import { AlertError } from '@terra-symposium/styling/ui';
35
- import { Button } from '@terra-symposium/styling/ui';
36
- import { ContextMenu, ContextMenuContent, ContextMenuTrigger } from '@terra-symposium/styling/ui';
37
- import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@terra-symposium/styling/ui';
38
- import { ErrorBoundary } from '../../ui.js';
32
+ import { AlertError, Button, ContextMenu, ContextMenuContent, ContextMenuTrigger, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Separator, Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarMenu, SidebarRail, Slider, } from '@terra-symposium/styling/ui';
39
33
  import { ExportDialog as SharedExportDialog } from './_export-dialog.js';
40
- import { Input } from '@terra-symposium/styling/ui';
41
- import { Label } from '@terra-symposium/styling/ui';
42
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@terra-symposium/styling/ui';
43
- import { Separator } from '@terra-symposium/styling/ui';
44
- import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarMenu, SidebarRail, } from '@terra-symposium/styling/ui';
45
- import { Slider } from '@terra-symposium/styling/ui';
34
+ import { ManageAttachmentsDialog } from './_manage-attachments-dialog.js';
35
+ // DATA
36
+ import { FILES } from '../../../../lib/terra/runtime/shared.js';
46
37
  // prettier-ignore
47
38
  /*//////////////////////////////////////////////////
48
39
  CONFIG
49
40
  //////////////////////////////////////////////////*/
50
- /**
51
- * SANITY MANY-TO-MANY ROUTING ARCHITECTURE
52
- *
53
- * Parses explicit Sanity IDs directly from the URL.
54
- * A single leaf or group can belong to multiple parents. Canonical routing keeps the active
55
- * section anchored to the first path segment so the sidebar select never drifts.
56
- *
57
- * @param pathname - Current browser pathname used to mark the active route.
58
- */
59
41
  /**
60
42
  * Reads the typed detail payload from a DOM event.
61
43
  *
62
44
  * @param e - DOM event carrying the custom payload.
63
- * @returns The event detail payload.
45
+ *
46
+ * @return The event detail payload.
64
47
  */
65
48
  const eDetail = (e) => e.detail;
66
49
  /**
67
- * Handles latest content.
50
+ * Reads the latest Markdown source from the content model for sidebar preview state.
68
51
  *
69
52
  * @param content - Docs content used to derive the sidebar structure.
53
+ *
54
+ * @return The source text, or an empty string when the model has no text content.
70
55
  */
71
56
  const latestContent = (content) => {
72
57
  if (typeof content === 'string')
@@ -74,20 +59,11 @@ const latestContent = (content) => {
74
59
  return Array.isArray(content) ? currLeafSrc(content, 'sidebar.latestContent') : '';
75
60
  };
76
61
  /*//////////////////////////////////////////////////
77
- FUNCTIONS
62
+ HELPERS
78
63
  //////////////////////////////////////////////////*/
79
- /**
80
- * Handles archive.
81
- *
82
- * @param v - Paths input supplied to archive's operation.
83
- * @param r - Root dir input supplied to archive's operation.
84
- * @param f - Filename base input supplied to archive's operation.
85
- * @param o - Opts input supplied to archive's operation.
86
- * @param d - Callback invoked for progress.
87
- * @param n - Node ids input supplied to archive's operation.
88
- */
64
+ /** Dialog implementations assigned below for the sidebar's typed component registry. */
89
65
  const SidebarComponents = {};
90
- /**
66
+ /*
91
67
  * RENDER TRIPWIRE
92
68
  * A diagnostic tool to catch and analyze React infinite loops.
93
69
  *
@@ -97,86 +73,64 @@ const SidebarComponents = {};
97
73
  /*//////////////////////////////////////////////////
98
74
  COMPONENTS
99
75
  //////////////////////////////////////////////////*/
100
- /*
101
- * Dialog component for selecting and managing all attachments across a section tree.
102
- *
103
- * @param props - Active attachment target, state setter, disabled state, and Docs tree for browsing and managing related assets.
104
- * @param tgt - The specific target node (Leaf, Group, or Section) receiving the attachment modifications.
105
- * @param setTgt - State dispatcher to clear or update the active target boundary, closing the dialog.
106
- * @param disabled - Master lock preventing interactions while network mutations or animations are active.
107
- * @param tree - Tree input supplied to manage attchs dialog's operation.
108
- *
109
- * @return The interactive modal overlay containing the attachment dashboard.
110
- *
111
- * @remarks
112
- * Provides a unified interface for uploading raw media, scraping external web links, linking YouTube videos,
113
- * and directly associating existing or new internal documentation pages as relational attachments.
114
- */
115
- SidebarComponents.PromptDialog = function PromptDialog({ state: s, setState: setS, disabled, }) {
116
- if (!s)
117
- return null;
118
- return (_jsx(Dialog, { open: s.open, onOpenChange: (o) => {
119
- if (!o) {
120
- s.onCancel?.();
121
- setS(null);
122
- }
123
- }, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: s.title }), _jsx(DialogDescription, { children: s.description || `Enter a new ${s.label.toLowerCase()}.` })] }), _jsx("div", { className: "py-4", children: _jsx(Input, { autoFocus: true, "aria-label": s.label, value: s.value, onChange: (e) => {
124
- setS({ ...s, value: e.target.value });
125
- }, className: "w-full focus:ring-0", disabled: disabled, onKeyDown: (e) => {
126
- if (e.key === 'Enter') {
127
- e.preventDefault();
76
+ SidebarComponents.PromptDialog =
77
+ /**
78
+ * Prompts for a new sidebar operation and reports the user's choice to the owning component.
79
+ *
80
+ * @param props Prompt state, state updater, and whether the current operation is disabled.
81
+ *
82
+ * @return The prompt dialog while a prompt is active, or `null` when no prompt is open.
83
+ */
84
+ function PromptDialog({ state: s, setState: setS, disabled, }) {
85
+ if (!s)
86
+ return null;
87
+ return (_jsx(Dialog, { open: s.open, onOpenChange: (o) => {
88
+ if (!o) {
89
+ s.onCancel?.();
90
+ setS(null);
91
+ }
92
+ }, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: s.title }), _jsx(DialogDescription, { children: s.description || `Enter a new ${s.label.toLowerCase()}.` })] }), _jsx("div", { className: "py-4", children: _jsx(Input, { autoFocus: true, "aria-label": s.label, value: s.value, onChange: (e) => {
93
+ setS({ ...s, value: e.target.value });
94
+ }, className: "w-full focus:ring-0", disabled: disabled, onKeyDown: (e) => {
95
+ if (e.key === 'Enter') {
96
+ e.preventDefault();
97
+ if (s.value.trim()) {
98
+ s.onSave(s.value.trim());
99
+ setS(null);
100
+ }
101
+ }
102
+ } }) }), _jsx(DialogFooter, { children: _jsx(Button, { disabled: disabled || !s.value.trim(), onClick: () => {
128
103
  if (s.value.trim()) {
129
104
  s.onSave(s.value.trim());
130
105
  setS(null);
131
106
  }
132
- }
133
- } }) }), _jsx(DialogFooter, { children: _jsx(Button, { disabled: disabled || !s.value.trim(), onClick: () => {
134
- if (s.value.trim()) {
135
- s.onSave(s.value.trim());
136
- setS(null);
137
- }
138
- }, children: "Save" }) })] }) }));
139
- };
140
- /*
141
- * Renders the delete dialog component.
142
- *
143
- * @param props - The selected deletion target, state setter, confirmation callback, and disabled state for the delete confirmation.
144
- * @param setTgt - Clears or updates the active target.
145
- * @param tgt - Tgt input supplied to delete dialog's operation.
146
- * @param disabled - Disabled state controlling whether delete dialog accepts interaction.
147
- * @param onConfirm - Callback invoked for confirm.
148
- *
149
- * @return The delete dialog or nothing when no target is selected.
150
- *
151
- * @remarks Renders the delete confirmation dialog for sidebar content.
152
- */
153
- SidebarComponents.DeleteDialog = function DeleteDialog({ tgt, setTgt, onConfirm, disabled, }) {
154
- if (!tgt)
155
- return null;
156
- return (_jsx(Dialog, { open: !!tgt, onOpenChange: (o) => {
157
- if (!o)
158
- setTgt(null);
159
- }, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsxs(DialogTitle, { children: ["Delete ", tgt.kind === 'leaf' ? 'page' : tgt.kind === 'branch' ? 'group' : 'section', "?"] }), _jsxs(DialogDescription, { children: ["Delete ", _jsx("code", { children: tgt.title || tgt.path }), ' ', tgt.kind !== 'leaf' ? ' and its nested docs.' : '.', " This action cannot be undone."] })] }), _jsx(DialogFooter, { children: _jsx(Button, { variant: "destructive", disabled: disabled, onClick: () => {
160
- onConfirm();
161
- }, children: "Delete" }) })] }) }));
162
- };
163
- /**
164
- * Renders the top collapsed header boundary on mobile-view structurally isolating context safely cleanly.
165
- *
166
- * @return {JSX.Element} Visual mobile heading frame.
167
- */
107
+ }, children: "Save" }) })] }) }));
108
+ };
109
+ SidebarComponents.DeleteDialog =
110
+ /**
111
+ * Confirms deletion of the selected page, group, or section before invoking the caller's action.
112
+ *
113
+ * @param props Selected deletion target, state updater, confirmation callback, and disabled state.
114
+ *
115
+ * @return The confirmation dialog while a target is selected, or `null` otherwise.
116
+ */
117
+ function DeleteDialog({ tgt, setTgt, onConfirm, disabled, }) {
118
+ if (!tgt)
119
+ return null;
120
+ return (_jsx(Dialog, { open: !!tgt, onOpenChange: (o) => {
121
+ if (!o)
122
+ setTgt(null);
123
+ }, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsxs(DialogTitle, { children: ["Delete ", tgt.kind === 'leaf' ? 'page' : tgt.kind === 'branch' ? 'group' : 'section', "?"] }), _jsxs(DialogDescription, { children: ["Delete ", _jsx("code", { children: tgt.title || tgt.path }), ' ', tgt.kind !== 'leaf' ? ' and its nested docs.' : '.', " This action cannot be undone."] })] }), _jsx(DialogFooter, { children: _jsx(Button, { variant: "destructive", disabled: disabled, onClick: () => {
124
+ onConfirm();
125
+ }, children: "Delete" }) })] }) }));
126
+ };
168
127
  /**
169
- * Main sidebar controller connecting context providers natively driving application visual trees.
128
+ * Coordinates the Docs sidebar's route selection, navigation tree, dialogs, and user actions.
170
129
  *
171
- * @param {object} props - Input definitions matching specific nav tree routes.
172
- * @param docs - Docs input supplied to nav's operation.
173
- * @param pathname - Pathname input supplied to nav's operation.
174
- * @param permission - Permission input supplied to nav's operation.
175
- * @param userData - user record supplied to nav.
176
- *
177
- * @return {JSX.Element} Fully formatted UI sidebar assembly correctly binding handlers.
130
+ * @param props Navigation tree, current path, access permission, optional user, and site labels.
131
+ * @returns The Docs sidebar and its navigation controls for the active host route.
178
132
  */
179
- export default (function Nav({ docs: items, pathname, permission, userData, siteTitle = 'Terra Symposium', siteDescription, homePath = '/', ...props }) {
133
+ function Nav({ docs: items, pathname, permission, userData, siteTitle = 'Terra Symposium', siteDescription, homePath = '/', ...props }) {
180
134
  // STATES
181
135
  const leafTitle = useAtom($leafTitle);
182
136
  const curMode = useAtom($mode);
@@ -186,6 +140,8 @@ export default (function Nav({ docs: items, pathname, permission, userData, site
186
140
  const [drag, setDrag] = useState(null);
187
141
  const [dState, setDState] = useState(null);
188
142
  const [hovered, setHovered] = useState(null);
143
+ // MANY-TO-MANY ROUTE ANCHOR
144
+ // Resolve the first route segment as the active section because leaves and groups can have multiple parents.
189
145
  const [cPathname, setCPathname] = useState(() => route(pathname || '').pathname);
190
146
  const urlIds = useMemo(() => route(cPathname), [cPathname]);
191
147
  const [viewedSecId, setViewedSecId] = useState(() => route(pathname || '').sectionId || null);
@@ -253,7 +209,15 @@ export default (function Nav({ docs: items, pathname, permission, userData, site
253
209
  const nestedBranchIds = new Set();
254
210
  s.forEach((n) => {
255
211
  if (n.children)
256
- (function walk(children) {
212
+ (
213
+ /**
214
+ * Collects nested branch IDs so child groups are not duplicated as top-level sections.
215
+ *
216
+ * @param children Child navigation nodes under the current section.
217
+ *
218
+ * @return Nothing; branch IDs are added to `nestedBranchIds` during traversal.
219
+ */
220
+ function walk(children) {
257
221
  children.forEach((c) => {
258
222
  if (c.type === 'branch') {
259
223
  nestedBranchIds.add(c.origName);
@@ -332,7 +296,13 @@ export default (function Nav({ docs: items, pathname, permission, userData, site
332
296
  */
333
297
  if (leafTitle?.path)
334
298
  setTree((nodes) => {
335
- /** Normalize full storage paths before matching a local title update. */
299
+ /**
300
+ * Normalizes stored paths before matching a local title update.
301
+ *
302
+ * @param value Host path, storage path, or route fragment.
303
+ *
304
+ * @return A slash-delimited path without draft markers, extensions, or query fragments.
305
+ */
336
306
  const cPath = (value) => value
337
307
  .replace(/\\/g, '/')
338
308
  .split(/[?#]/, 1)[0]
@@ -341,28 +311,42 @@ export default (function Nav({ docs: items, pathname, permission, userData, site
341
311
  .replace(/^\/+|\/+$/g, '');
342
312
  const targetPath = cPath(leafTitle.path);
343
313
  const targetBasename = targetPath.split('/').at(-1)?.toLowerCase() ?? '';
344
- const countBasename = (items) => items.reduce((count, item) => {
345
- if (item.type === 'leaf') {
346
- const basename = cPath(item.path || '')
314
+ /**
315
+ * Counts leaves with the target basename before enabling a basename-only title match.
316
+ *
317
+ * @param items Navigation nodes whose leaves should be counted.
318
+ *
319
+ * @return Number of leaves whose normalized basename matches the target.
320
+ */
321
+ const countBasename = (items) => items.reduce((count, i) => {
322
+ if (i.type === 'leaf') {
323
+ const basename = cPath(i.path || '')
347
324
  .split('/')
348
325
  .at(-1)
349
326
  ?.toLowerCase();
350
327
  return count + Number(basename === targetBasename);
351
328
  }
352
- return count + countBasename(item.children ?? []);
329
+ return count + countBasename(i.children ?? []);
353
330
  }, 0);
354
331
  // Many repository documents are named index.md. A basename fallback
355
332
  // would rename every such leaf, so only allow it for a unique filename.
356
333
  const canMatchBasename = targetBasename !== 'index' && targetBasename !== '' && countBasename(nodes) === 1;
334
+ /**
335
+ * Returns a copied tree with the matching leaf title replaced.
336
+ *
337
+ * @param items Navigation nodes to copy and update recursively.
338
+ *
339
+ * @return A new tree whose matching leaf uses the latest title.
340
+ */
357
341
  const update = (items) => items.map((node) => {
358
342
  const nodePath = node.type === 'leaf' ? node.path || '' : '';
359
- const normalizedNodePath = cPath(nodePath);
343
+ const normNodePath = cPath(nodePath);
360
344
  if (node.type === 'leaf' &&
361
345
  (nodePath === leafTitle.path ||
362
346
  node.origName === leafTitle.path ||
363
- normalizedNodePath === targetPath ||
347
+ normNodePath === targetPath ||
364
348
  (canMatchBasename &&
365
- normalizedNodePath.split('/').at(-1)?.toLowerCase() === targetBasename))) {
349
+ normNodePath.split('/').at(-1)?.toLowerCase() === targetBasename))) {
366
350
  return { ...node, title: leafTitle.title };
367
351
  }
368
352
  if (node.type === 'branch' && node.children)
@@ -467,7 +451,15 @@ export default (function Nav({ docs: items, pathname, permission, userData, site
467
451
  *
468
452
  * @param nodes - Sidebar nodes traversed while collecting leaf paths.
469
453
  */
470
- (function walk(nodes) {
454
+ (
455
+ /**
456
+ * Collects leaf paths in the same order they appear in the navigation tree.
457
+ *
458
+ * @param nodes Navigation nodes to traverse.
459
+ *
460
+ * @return Nothing; each discovered leaf path is appended to `allLeaves`.
461
+ */
462
+ function walk(nodes) {
471
463
  nodes.forEach((n) => {
472
464
  if (n.type === 'leaf' && n.path)
473
465
  allLeaves.push(n.path);
@@ -500,7 +492,16 @@ export default (function Nav({ docs: items, pathname, permission, userData, site
500
492
  }, []);
501
493
  const mergeLeaves = useCallback((v) => {
502
494
  const leafOrder = new Map();
503
- (function walk(nodes, order = 0) {
495
+ (
496
+ /**
497
+ * Records each leaf's traversal index so merged pages retain sidebar order.
498
+ *
499
+ * @param nodes Navigation subtree to index.
500
+ * @param order Next traversal index to assign.
501
+ *
502
+ * @return The next index after this subtree has been visited.
503
+ */
504
+ function walk(nodes, order = 0) {
504
505
  for (const node of nodes) {
505
506
  if (node.type === 'leaf' && node.path)
506
507
  leafOrder.set(node.path, order++);
@@ -532,7 +533,22 @@ export default (function Nav({ docs: items, pathname, permission, userData, site
532
533
  *
533
534
  * @param items - Sidebar items being pruned from the tree.
534
535
  */
535
- return (function prune(items) {
536
+ return ( /**
537
+ return ( * return ( * return ( *
538
+ return ( * return ( * return ( * @param items
539
+ return ( * return ( * return ( *
540
+ return ( * return ( * return ( * @return
541
+ return ( * return ( * return (
542
+ return ( * return ( *
543
+ return ( * return ( * @param items
544
+ return ( * return ( *
545
+ return ( * return ( * @return
546
+ return ( * return (
547
+ return ( *
548
+ return ( * @param items
549
+ return ( *
550
+ return ( * @return
551
+ return ( */function prune(items) {
536
552
  const n = [];
537
553
  for (const node of items) {
538
554
  if (node.type === 'leaf') {
@@ -882,7 +898,25 @@ export default (function Nav({ docs: items, pathname, permission, userData, site
882
898
  url = aParentId === sectId ? buildRoute(sectId, undefined, tLeafId) : buildRoute(sectId, aParentId, tLeafId);
883
899
  setTree(v => {
884
900
  const nTree = JSON.parse(JSON.stringify(v));
885
- (function insertLeaf(nodes, tgtId) {
901
+ ( /**
902
+ ( * ( * ( *
903
+ ( * ( * ( * @param nodes
904
+ ( * ( * ( * @param tgtId
905
+ ( * ( * ( *
906
+ ( * ( * ( * @return
907
+ ( * ( * (
908
+ ( * ( *
909
+ ( * ( * @param nodes
910
+ ( * ( * @param tgtId
911
+ ( * ( *
912
+ ( * ( * @return
913
+ ( * (
914
+ ( *
915
+ ( * @param nodes
916
+ ( * @param tgtId
917
+ ( *
918
+ ( * @return
919
+ ( */function insertLeaf(nodes, tgtId) {
886
920
  for (const n of nodes) {
887
921
  if (n.type === 'branch') {
888
922
  if (n.origName === tgtId) {
@@ -952,7 +986,25 @@ export default (function Nav({ docs: items, pathname, permission, userData, site
952
986
  const gUrl = buildRoute(rootId, tGrpId);
953
987
  setTree(v => {
954
988
  const nTree = JSON.parse(JSON.stringify(v));
955
- (function insertGroup(nodes, tgtId) {
989
+ ( /**
990
+ ( * ( * ( *
991
+ ( * ( * ( * @param nodes
992
+ ( * ( * ( * @param tgtId
993
+ ( * ( * ( *
994
+ ( * ( * ( * @return
995
+ ( * ( * (
996
+ ( * ( *
997
+ ( * ( * @param nodes
998
+ ( * ( * @param tgtId
999
+ ( * ( *
1000
+ ( * ( * @return
1001
+ ( * (
1002
+ ( *
1003
+ ( * @param nodes
1004
+ ( * @param tgtId
1005
+ ( *
1006
+ ( * @return
1007
+ ( */function insertGroup(nodes, tgtId) {
956
1008
  for (const n of nodes) {
957
1009
  if (n.type === 'branch') {
958
1010
  if (n.origName === tgtId) {
@@ -1013,7 +1065,17 @@ export default (function Nav({ docs: items, pathname, permission, userData, site
1013
1065
  * @param tgtPath - Target document path being resolved.
1014
1066
  * @param currPath - Current traversal path accumulated during the search.
1015
1067
  */
1016
- const pPath = (function findParentPath(nodes, tgtPath, currPath = '') {
1068
+ const pPath = (
1069
+ /**
1070
+ * Finds the containing branch path for a leaf before moving it.
1071
+ *
1072
+ * @param nodes Navigation subtree to search.
1073
+ * @param tgtPath Storage path of the leaf being moved.
1074
+ * @param currPath Branch path accumulated during traversal.
1075
+ *
1076
+ * @return Parent branch path, or `null` when the leaf is absent.
1077
+ */
1078
+ function findParentPath(nodes, tgtPath, currPath = '') {
1017
1079
  for (const n of nodes) {
1018
1080
  if (n.type === 'leaf' && n.path === tgtPath)
1019
1081
  return currPath || '.';
@@ -1396,7 +1458,7 @@ export default (function Nav({ docs: items, pathname, permission, userData, site
1396
1458
  });
1397
1459
  }, children: "Reset" }), _jsx(Button, { disabled: loading || transitioning, loading: loading, onClick: () => {
1398
1460
  SidebarActions.saveHue();
1399
- }, children: "Save" })] })] }) }), _jsxs(SidebarHeader, { className: "print:hidden", "data-docs-navigation": "true", children: [_jsx("a", { className: "font-serif text-2xl italic hover:tracking-tight", href: homePath, children: siteTitle }), siteDescription && _jsx("p", { className: "m-0 mt-1 line-clamp-1 text-sm text-primary-11", children: siteDescription }), (sects.length > 1 || permission === 'write') && (_jsx("div", { className: "mt-2 flex items-center", children: _jsx("div", { className: cn('block min-w-0 flex-1 rounded-md transition-opacity'), children: _jsxs(Select, { disabled: transitioning, open: sectionOpen, onOpenChange: setSectionOpen, value: aViewed ? aViewed.origName : '', onValueChange: async (v) => {
1461
+ }, children: "Save" })] })] }) }), _jsxs(SidebarHeader, { className: "print:hidden", "data-docs-navigation": "true", children: [_jsx("a", { className: "font-serif text-2xl italic hover:tracking-tight", href: homePath, children: siteTitle }), siteDescription && (_jsx("p", { className: "m-0 mt-1 line-clamp-1 text-sm text-primary-11", children: siteDescription })), (sects.length > 1 || permission === 'write') && (_jsx("div", { className: "mt-2 flex items-center", children: _jsx("div", { className: cn('block min-w-0 flex-1 rounded-md transition-opacity'), children: _jsxs(Select, { disabled: transitioning, open: sectionOpen, onOpenChange: setSectionOpen, value: aViewed ? aViewed.origName : '', onValueChange: async (v) => {
1400
1462
  if (v === '__add_section__') {
1401
1463
  setTimeout(() => {
1402
1464
  addSection();
@@ -1432,9 +1494,10 @@ export default (function Nav({ docs: items, pathname, permission, userData, site
1432
1494
  treeCtx.addGroup(aViewed.origName, aViewed.origName);
1433
1495
  },
1434
1496
  },
1435
- ] }) }))] }), _jsxs(SidebarFooter, { children: [_jsx(User, { user: userData ?? null, className: "mr-auto pl-0" }), _jsx("button", { type: "button", className: "ml-auto rounded px-1 text-xs text-primary-11 hover:text-primary-12", "aria-label": "Search documentation", title: "Search docs (\u2318K)", onClick: (event) => {
1436
- event.stopPropagation();
1497
+ ] }) }))] }), _jsxs(SidebarFooter, { children: [_jsx(User, { user: userData ?? null, className: "mr-auto pl-0" }), _jsx(Button, { variant: "primary-underline", "aria-label": "Search \u2318K", title: "Search docs (\u2318K)", onClick: (e) => {
1498
+ e.stopPropagation();
1437
1499
  window.dispatchEvent(new Event('docs:open-search'));
1438
1500
  }, children: "Search docs" })] }), _jsx(SidebarRail, {})] }) }));
1439
- });
1501
+ }
1502
+ export default Nav;
1440
1503
  //# sourceMappingURL=_sidebar.js.map