@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.
@@ -1,4 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * src/components/terra/pages/docs/_toolbar.tsx
4
+ */
2
5
  // @ts-nocheck -- source-preserving extraction; public host contracts are typed in src/host.ts.
3
6
  /**
4
7
  * astro/src/pages/docs/_toolbar.tsx
@@ -9,41 +12,31 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
9
12
  IMPORTS
10
13
  //////////////////////////////////////////////////*/
11
14
  // API
12
- import { clearTransition, setTransition, useAtom } from '../../../../lib/terra/docs/state.js';
13
15
  import { $attachmentFilter } from '../../../../lib/terra/docs/attachments.js';
14
- import { codeViewOf } from '../../../../lib/terra/docs/editor-dom.js';
15
- import { FindReplace } from './_toolbar-find-replace.js';
16
- import { toolbarFocusValue } from '../../../../lib/terra/docs/toolbar.js';
17
- import { isDocBody } from '../../../../lib/terra/runtime/types.js';
18
16
  import { $createHeadingNode } from '@lexical/rich-text';
19
17
  import { $createParagraphNode, $getNearestNodeFromDOMNode, $isElementNode } from 'lexical';
20
18
  import { actions } from '../../../../lib/terra/runtime/action-proxy.js';
21
- import { dirname, docsRootPath, toPascal } from '../../../../lib/terra/runtime/shared.js';
19
+ import { clearTransition, setTransition, useAtom } from '../../../../lib/terra/docs/state.js';
22
20
  import { cn, notify } from '../../../../lib/terra/runtime/runtime.js';
21
+ import { codeViewOf } from '../../../../lib/terra/docs/editor-dom.js';
23
22
  import { components as repoComponents } from '../../../../lib/terra/custom-components.js';
23
+ import { dirname, docsRootPath, toPascal } from '../../../../lib/terra/runtime/shared.js';
24
+ import { getDocsHostAdapter } from '../../../../lib/host-runtime.js';
25
+ import { isDocBody } from '../../../../lib/terra/runtime/types.js';
24
26
  import { redo, redoDepth, undo, undoDepth } from '@codemirror/commands';
25
27
  import { stringifyDocBody } from '../../../../lib/terra/runtime/content/document-markdown.js';
26
28
  import { toast } from 'sonner';
29
+ import { toolbarFocusValue } from '../../../../lib/terra/docs/toolbar.js';
27
30
  import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
28
- import { getDocsHostAdapter } from '../../../../lib/host-runtime.js';
29
31
  // COMPONENTS
30
32
  import * as TerraQualityRegex from '../../../../lib/terra/regex.mjs';
31
33
  import React from 'react';
32
- import { TextTooltip, TooltipProvider } from '@terra-symposium/styling/ui';
33
34
  import { ArrowUpRight as PopOutIcon, CaretDown as CaretDownIcon, DataShare as ShareIcon, Image as ImageIcon, Link as LinkIcon, ListBulleted as BulletListIcon, ListNumbered as NumberListIcon, Paste as PasteIcon, Redo as RedoIcon, RepoSourceCode as ModuleIcon, Search as SearchIcon, Task as CheckListIcon, TextBold as BoldIcon, TextFont as TextFontIcon, TextItalic as ItalicIcon, TextStrikethrough as StrikethroughIcon, TextUnderline as UnderlineIcon, Undo as UndoIcon, } from '@carbon/icons-react';
34
- import { Button as UiButton } from '@terra-symposium/styling/ui';
35
- import { ButtonGroup } from '@terra-symposium/styling/ui';
35
+ import { Button as UiButton, ButtonGroup, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Input, Label, Popover, PopoverContent, PopoverTrigger, SearchEmpty, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, TextTooltip, ToggleGroup, ToggleGroupItem, TooltipProvider, } from '@terra-symposium/styling/ui';
36
36
  import { CodeBlock } from '../../editor/docs/_CodeBlock.js';
37
- import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@terra-symposium/styling/ui';
38
- import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from '@terra-symposium/styling/ui';
39
37
  import { EditStateCtx } from '../../../../lib/terra/docs/state.js';
40
38
  import { ErrorBoundary } from '../../ui.js';
41
- import { Input } from '@terra-symposium/styling/ui';
42
- import { Label } from '@terra-symposium/styling/ui';
43
- import { Popover, PopoverContent, PopoverTrigger } from '@terra-symposium/styling/ui';
44
- import { SearchEmpty } from '@terra-symposium/styling/ui';
45
- import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, } from '@terra-symposium/styling/ui';
46
- import { ToggleGroup, ToggleGroupItem } from '@terra-symposium/styling/ui';
39
+ import { FindReplace } from './_toolbar-find-replace.js';
47
40
  // DATA
48
41
  import { ATTCH_TYPES } from '../../../../lib/terra/docs/attachments.js';
49
42
  import { DOCS_BLOCK_ICONS } from './_icons.js';
@@ -68,11 +61,13 @@ const MENU_WIDTH_CLASS = 'w-56';
68
61
  HELPERS
69
62
  //////////////////////////////////////////////////*/
70
63
  /**
71
- * Handles code offset.
64
+ * Measures the visible text offset of a DOM position within the active editor.
72
65
  *
73
- * @param content - Docs content containing the target block.
74
- * @param node - Block node being transformed by the toolbar action.
75
- * @param value - Replacement value supplied by the toolbar control.
66
+ * @param content Editor element whose rendered text supplies the offset basis.
67
+ * @param node DOM node containing the position being measured.
68
+ * @param val Offset within `node` at which the range should end.
69
+ *
70
+ * @return Character count before the position, or the full editor text length when the node is outside it.
76
71
  */
77
72
  const codeOffset = (content, node, val) => {
78
73
  if (!content.contains(node))
@@ -82,14 +77,6 @@ const codeOffset = (content, node, val) => {
82
77
  range.setEnd(node, val);
83
78
  return range.toString().length;
84
79
  };
85
- /**
86
- * Derives a clean, human-readable label from a raw filename or URL.
87
- *
88
- * @param {string} v - The raw filename or path string.
89
- * @param {string} fallback - The default label if extraction fails.
90
- *
91
- * @return {string} The formatted, capitalized label.
92
- */
93
80
  /**
94
81
  * Prevents the active selection range from being lost when clicking toolbar buttons.
95
82
  *
@@ -152,12 +139,18 @@ function looksLikeJsonBlob(val) {
152
139
  // prettier-ignore
153
140
  return ['{}', '[]'].some(p => v.startsWith(p[0]) && v.endsWith(p[1])) && v.length > 80;
154
141
  }
155
- /*//////////////////////////////////////////////////
156
- COMPONENTS
157
- //////////////////////////////////////////////////*/
158
- const Button = React.forwardRef(function Button({ children: c, className: v, disabled: d, lbl: l, shortcut: s, onClick: o, onMouseDown: r, active: a, ...p }, x) {
142
+ const Button = React.forwardRef(
143
+ /**
144
+ * Renders the toolbar's compact button with its label and optional shortcut hint.
145
+ *
146
+ * @param props Button content, label, shortcut, active state, and native button attributes.
147
+ * @param ref Forwarded reference attached to the rendered button.
148
+ * @returns The styled button wrapped in its tooltip.
149
+ */
150
+ function Button(props, ref) {
151
+ const { children: c, className: v, disabled: d, lbl: l, shortcut: s, onClick: o, onMouseDown: r, active: a, ...p } = props;
159
152
  void a;
160
- return (_jsx(TextTooltip, { content: s ? `${l} (${s})` : l, side: "top", align: "center", delayDuration: 400, children: _jsx(UiButton, { ref: x, variant: "ghost", size: "xs", "aria-label": l, disabled: d, onClick: o, onMouseDown: r, className: cn('inline-flex h-6 !p-0.5 shadow-none', v), ...p, children: c }) }));
153
+ return (_jsx(TextTooltip, { content: s ? `${l} (${s})` : l, side: "top", align: "center", delayDuration: 400, children: _jsx(UiButton, { ref: ref, variant: "ghost", size: "xs", "aria-label": l, disabled: d, onClick: o, onMouseDown: r, className: cn('inline-flex h-6 !p-0.5 shadow-none', v), ...p, children: c }) }));
161
154
  });
162
155
  Button.displayName = 'Button';
163
156
  /**
@@ -181,18 +174,15 @@ function Mode({ mode, onModeChange, disabled, editable, size = 'xs', }) {
181
174
  onModeChange(n);
182
175
  }, children: [_jsx(SelectTrigger, { "aria-label": "Mode", width: "fit", children: _jsx(SelectValue, { placeholder: "Mode" }) }), _jsx(SelectContent, { align: 'start', children: (editable ? ['view', 'edit', 'attachments'] : ['view', 'attachments']).map(o => _jsx(SelectItem, { value: o, children: o[0].toUpperCase() + o.slice(1) }, o)) })] }));
183
176
  }
184
- /**
185
- * Renders Docs-specific controls beside a content editor toolbar.
186
- *
187
- * Formatting and content insertion remain editor-owned; these controls manage
188
- * the document shell's mode and persistence state.
189
- *
190
- * @param props - Docs toolbar state and current mode.
191
- *
192
- * @return The mode, history, save, or version actions supported by the active Docs editor toolbar.
193
- */
194
177
  const TOOLBAR_COMPONENTS = {
195
- DocsToolbarActions({ activeMode, editable, size = 'xs', toolbar, }) {
178
+ /**
179
+ * Adds Docs-specific mode, history, save, and version controls beside an editor toolbar.
180
+ *
181
+ * @param props Active mode, editability, control size, and toolbar state.
182
+ *
183
+ * @return Supported Docs actions, or `null` when the host is read-only.
184
+ */
185
+ DocsToolbarActions({ activeMode: aMode, editable, size = 'xs', toolbar, }) {
196
186
  const edit = useContext(EditStateCtx);
197
187
  const [versions, setVersions] = useState([]);
198
188
  const [historyOpen, setHistoryOpen] = useState(false);
@@ -224,9 +214,9 @@ const TOOLBAR_COMPONENTS = {
224
214
  if (live && Array.isArray(data))
225
215
  setVersions(data);
226
216
  }
227
- catch (error) {
228
- console.error('[Toolbar:versions] load-failed', { path, error });
229
- toast.error(error instanceof Error ? error.message : 'Failed to load doc versions.');
217
+ catch (err) {
218
+ console.error('[Toolbar:versions] load-failed', { path, error: err });
219
+ toast.error(err instanceof Error ? err.message : 'Failed to load doc versions.');
230
220
  }
231
221
  })();
232
222
  return () => {
@@ -237,6 +227,13 @@ const TOOLBAR_COMPONENTS = {
237
227
  if (historyOpen && !selectedVersionId && versions[0])
238
228
  setSelectedVersionId(versions[0].savedAt);
239
229
  }, [historyOpen, selectedVersionId, versions]);
230
+ /**
231
+ * Saves the current document while preventing the toolbar form's default submission.
232
+ *
233
+ * @param event Form event whose browser submission must be cancelled.
234
+ *
235
+ * @return A promise that settles after the save callback finishes or reports an error.
236
+ */
240
237
  const save = async (event) => {
241
238
  event.preventDefault();
242
239
  if (!toolbar?.save)
@@ -244,24 +241,31 @@ const TOOLBAR_COMPONENTS = {
244
241
  try {
245
242
  await Promise.resolve(toolbar.save());
246
243
  }
247
- catch (error) {
248
- console.error('[Docs:save] failed', error);
249
- toast.error(error instanceof Error ? error.message : 'Unable to save document.');
244
+ catch (err) {
245
+ console.error('[Docs:save] failed', err);
246
+ toast.error(err instanceof Error ? err.message : 'Unable to save document.');
250
247
  }
251
248
  };
252
249
  useEffect(() => {
253
- if (activeMode === 'view')
250
+ if (aMode === 'view')
254
251
  return;
252
+ /**
253
+ * Saves the document when the user presses the platform save shortcut.
254
+ *
255
+ * @param event Keyboard event dispatched by the document.
256
+ *
257
+ * @return Nothing; matching shortcuts delegate to the save handler.
258
+ */
255
259
  const onKeyDown = (event) => {
256
260
  if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 's')
257
261
  void save(event);
258
262
  };
259
263
  document.addEventListener('keydown', onKeyDown);
260
264
  return () => document.removeEventListener('keydown', onKeyDown);
261
- }, [activeMode, toolbar]);
265
+ }, [aMode, toolbar]);
262
266
  if (!editable)
263
267
  return null;
264
- return (_jsx(TooltipProvider, { children: _jsxs("div", { className: "flex items-center gap-1", "data-docs-toolbar-actions": true, children: [_jsx(Mode, { editable: editable, mode: activeMode, size: size, disabled: !toolbar, onModeChange: (mode) => {
268
+ return (_jsx(TooltipProvider, { children: _jsxs("div", { className: "flex items-center gap-1", "data-docs-toolbar-actions": true, children: [_jsx(Mode, { editable: editable, mode: aMode, size: size, disabled: !toolbar, onModeChange: (mode) => {
265
269
  if (!toolbar)
266
270
  return;
267
271
  void toolbar.setMode(mode).catch((error) => {
@@ -269,7 +273,7 @@ const TOOLBAR_COMPONENTS = {
269
273
  toast.error(error instanceof Error ? error.message : 'Failed to change mode.');
270
274
  clearTransition();
271
275
  });
272
- } }), activeMode === 'edit' && (_jsxs(ButtonGroup, { children: [_jsx(TextTooltip, { content: "Save (\u2318S)", side: "top", align: "center", delayDuration: 400, children: _jsx(UiButton, { size: size, disabled: !toolbar || !toolbar.dirty, loading: toolbar?.busy === 'saving', onMouseDown: preserveSel, onClick: (event) => void save(event), children: "Save" }) }), historyEnabled && (_jsx(TextTooltip, { content: "Version History", side: "top", align: "center", delayDuration: 400, children: _jsx(UiButton, { size: size, className: "px-0.5", disabled: !toolbar, "aria-label": "Version History", onMouseDown: preserveSel, onClick: () => {
276
+ } }), aMode === 'edit' && (_jsxs(ButtonGroup, { children: [_jsx(TextTooltip, { content: "Save (\u2318S)", side: "top", align: "center", delayDuration: 400, children: _jsx(UiButton, { size: size, disabled: !toolbar || !toolbar.dirty, loading: toolbar?.busy === 'saving', onMouseDown: preserveSel, onClick: (event) => void save(event), children: "Save" }) }), historyEnabled && (_jsx(TextTooltip, { content: "Version History", side: "top", align: "center", delayDuration: 400, children: _jsx(UiButton, { size: size, className: "px-0.5", disabled: !toolbar, "aria-label": "Version History", onMouseDown: preserveSel, onClick: () => {
273
277
  if (versions.length > 0 && !selectedVersionId)
274
278
  setSelectedVersionId(versions[0].savedAt);
275
279
  setHistoryOpen(true);
@@ -311,6 +315,7 @@ const TOOLBAR_COMPONENTS = {
311
315
  let frame = 0;
312
316
  /**
313
317
  * Handles sync.
318
+ *
314
319
  */
315
320
  const sync = () => {
316
321
  const view = codeViewOf();
@@ -322,6 +327,7 @@ const TOOLBAR_COMPONENTS = {
322
327
  sync();
323
328
  /**
324
329
  * Handles refresh.
330
+ *
325
331
  */
326
332
  const refresh = () => {
327
333
  cancelAnimationFrame(frame);
@@ -664,10 +670,12 @@ const TOOLBAR_COMPONENTS = {
664
670
  }, [search.query, search.tree]);
665
671
  // 5. EMBED OR CREATE LEAF
666
672
  /**
667
- * Handles embed.
673
+ * Creates a page when requested and links the destination into the current document.
674
+ *
675
+ * @param tgtPath Existing destination path, or the initial path for a new page.
676
+ * @param isNew Whether a new page should be created before linking it.
668
677
  *
669
- * @param targetPath - Destination path for the moved or renamed document.
670
- * @param isNew - Whether the destination represents a newly created document.
678
+ * @return A promise that settles after the page link is inserted and the editor is restored.
671
679
  */
672
680
  const embed = async (tgtPath, isNew = false) => {
673
681
  let finalPath = tgtPath;
@@ -904,6 +912,7 @@ const TOOLBAR_COMPONENTS = {
904
912
  sync();
905
913
  /**
906
914
  * Handles refresh.
915
+ *
907
916
  */
908
917
  const refresh = () => {
909
918
  cancelAnimationFrame(frame);
@@ -1065,6 +1074,7 @@ const TOOLBAR_COMPONENTS = {
1065
1074
  useEffect(() => {
1066
1075
  /**
1067
1076
  * Handles sync.
1077
+ *
1068
1078
  */
1069
1079
  const sync = () => {
1070
1080
  const view = codeViewOf();
@@ -1090,6 +1100,11 @@ const TOOLBAR_COMPONENTS = {
1090
1100
  const saving = toolbar?.busy ?? null;
1091
1101
  const dirty = toolbar?.dirty ?? false;
1092
1102
  const publishDraft = getDocsHostAdapter()?.publishDraft;
1103
+ /**
1104
+ * Saves pending edits and publishes the active document through the host adapter.
1105
+ *
1106
+ * @return A promise that settles after publishing or reports a user-facing error.
1107
+ */
1093
1108
  const publish = async () => {
1094
1109
  if (!publishDraft || !toolbar?.path)
1095
1110
  return;
@@ -1099,8 +1114,8 @@ const TOOLBAR_COMPONENTS = {
1099
1114
  await publishDraft(toolbar.path);
1100
1115
  toast.success('Published.');
1101
1116
  }
1102
- catch (error) {
1103
- toast.error(error instanceof Error ? error.message : 'Unable to publish document.');
1117
+ catch (err) {
1118
+ toast.error(err instanceof Error ? err.message : 'Unable to publish document.');
1104
1119
  }
1105
1120
  };
1106
1121
  useEffect(() => {
@@ -1207,9 +1222,11 @@ const TOOLBAR_COMPONENTS = {
1207
1222
  };
1208
1223
  const insert = toolbar?.insert ?? adapter?.insert;
1209
1224
  /**
1210
- * Handles json command.
1225
+ * Dispatches a structured text command when the editor is in edit mode.
1211
1226
  *
1212
1227
  * @param detail - Toolbar detail describing the requested document update.
1228
+ *
1229
+ * @return `true` after dispatching in edit mode, or `false` when the editor is read-only.
1213
1230
  */
1214
1231
  const jsonCmd = (detail) => {
1215
1232
  if (mode !== 'edit')
@@ -1218,10 +1235,12 @@ const TOOLBAR_COMPONENTS = {
1218
1235
  return true;
1219
1236
  };
1220
1237
  /**
1221
- * Handles json block.
1238
+ * Creates a Portable Text block with a fresh key and the supplied field data.
1239
+ *
1240
+ * @param _type Portable Text block type assigned by the caller.
1241
+ * @param val Block fields copied into the new object.
1222
1242
  *
1223
- * @param _type - Sanity field type associated with the value.
1224
- * @param value - Field value being normalized for the toolbar.
1243
+ * @return A block object with a new `_key`, the requested type, and supplied fields.
1225
1244
  */
1226
1245
  const jsonBlock = (_type, val) => ({
1227
1246
  _key: crypto.randomUUID(),
@@ -1235,7 +1254,7 @@ const TOOLBAR_COMPONENTS = {
1235
1254
  * @param fallback - Value passed to the helper.
1236
1255
  * @param isInline - Value passed to the helper.
1237
1256
  *
1238
- * @returns safe Sel.
1257
+ * @return safe Sel.
1239
1258
  */
1240
1259
  const sSel = (fallback, isInline = false) => {
1241
1260
  let text = '';