@arbidocs/blocks 0.4.106 → 0.4.107

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 (112) hide show
  1. package/bin/arbidocs-render-take.mjs +27 -0
  2. package/dist/NativeDocxEditor-3VKJT43D.js +10 -0
  3. package/dist/NativeDocxEditor-3VKJT43D.js.map +1 -0
  4. package/dist/NativeDocxEditor-GNG5QV7V.cjs +31 -0
  5. package/dist/NativeDocxEditor-GNG5QV7V.cjs.map +1 -0
  6. package/dist/agents/index.cjs +209 -209
  7. package/dist/agents/index.js +4 -4
  8. package/dist/chat/index.cjs +125 -125
  9. package/dist/chat/index.js +3 -3
  10. package/dist/{chunk-PIT2YZCK.js → chunk-2PYENCW5.js} +3 -3
  11. package/dist/{chunk-PIT2YZCK.js.map → chunk-2PYENCW5.js.map} +1 -1
  12. package/dist/{chunk-W2RTZOK3.js → chunk-2SJTXYRM.js} +1749 -1778
  13. package/dist/chunk-2SJTXYRM.js.map +1 -0
  14. package/dist/{chunk-S7RHJI5Q.js → chunk-7NLPKJV5.js} +3 -3
  15. package/dist/{chunk-S7RHJI5Q.js.map → chunk-7NLPKJV5.js.map} +1 -1
  16. package/dist/{chunk-ZWWN4CUL.cjs → chunk-BHKFENKT.cjs} +1748 -1778
  17. package/dist/chunk-BHKFENKT.cjs.map +1 -0
  18. package/dist/{chunk-3R6XBYL7.cjs → chunk-DTBSGG36.cjs} +7 -2
  19. package/dist/chunk-DTBSGG36.cjs.map +1 -0
  20. package/dist/{chunk-3Y6NQIDE.js → chunk-EB7QAZLH.js} +5 -5
  21. package/dist/{chunk-3Y6NQIDE.js.map → chunk-EB7QAZLH.js.map} +1 -1
  22. package/dist/{chunk-TP3FAP5J.cjs → chunk-FZGMY6NQ.cjs} +5 -5
  23. package/dist/{chunk-TP3FAP5J.cjs.map → chunk-FZGMY6NQ.cjs.map} +1 -1
  24. package/dist/{chunk-W7NZRZ54.js → chunk-GFYGYME3.js} +3 -3
  25. package/dist/{chunk-W7NZRZ54.js.map → chunk-GFYGYME3.js.map} +1 -1
  26. package/dist/chunk-GYAUJ4KT.js +71 -0
  27. package/dist/chunk-GYAUJ4KT.js.map +1 -0
  28. package/dist/{chunk-XMBYDLZQ.cjs → chunk-HUJLKDWX.cjs} +4 -4
  29. package/dist/{chunk-XMBYDLZQ.cjs.map → chunk-HUJLKDWX.cjs.map} +1 -1
  30. package/dist/{chunk-4GFB5U3K.js → chunk-LHBJJ2GE.js} +4 -3
  31. package/dist/chunk-LHBJJ2GE.js.map +1 -0
  32. package/dist/{chunk-MP6FK43E.cjs → chunk-O5NDG4EA.cjs} +3 -3
  33. package/dist/{chunk-MP6FK43E.cjs.map → chunk-O5NDG4EA.cjs.map} +1 -1
  34. package/dist/{chunk-5YXG342J.cjs → chunk-OR4CO743.cjs} +3 -3
  35. package/dist/{chunk-5YXG342J.cjs.map → chunk-OR4CO743.cjs.map} +1 -1
  36. package/dist/{chunk-LGX6UFYG.js → chunk-P2KPDPFF.js} +8 -3
  37. package/dist/chunk-P2KPDPFF.js.map +1 -0
  38. package/dist/{chunk-DDF5GPIJ.js → chunk-P5ST47VY.js} +3 -3
  39. package/dist/{chunk-DDF5GPIJ.js.map → chunk-P5ST47VY.js.map} +1 -1
  40. package/dist/{chunk-EWSEQEPL.js → chunk-PFA43QNX.js} +3 -3
  41. package/dist/{chunk-EWSEQEPL.js.map → chunk-PFA43QNX.js.map} +1 -1
  42. package/dist/{chunk-7LLLQUM7.cjs → chunk-Q57MEBCK.cjs} +6 -5
  43. package/dist/chunk-Q57MEBCK.cjs.map +1 -0
  44. package/dist/{chunk-LLFW2T6S.cjs → chunk-R6APX23R.cjs} +11 -11
  45. package/dist/{chunk-LLFW2T6S.cjs.map → chunk-R6APX23R.cjs.map} +1 -1
  46. package/dist/{chunk-24ND4S6Y.cjs → chunk-RILMICOP.cjs} +5 -5
  47. package/dist/{chunk-24ND4S6Y.cjs.map → chunk-RILMICOP.cjs.map} +1 -1
  48. package/dist/chunk-S24XEVPS.cjs +73 -0
  49. package/dist/chunk-S24XEVPS.cjs.map +1 -0
  50. package/dist/{chunk-XQDM6T6C.js → chunk-WDD255XQ.js} +3 -3
  51. package/dist/{chunk-XQDM6T6C.js.map → chunk-WDD255XQ.js.map} +1 -1
  52. package/dist/{chunk-Z5VUZ6YD.cjs → chunk-ZV3BKLFY.cjs} +3 -3
  53. package/dist/{chunk-Z5VUZ6YD.cjs.map → chunk-ZV3BKLFY.cjs.map} +1 -1
  54. package/dist/collaboration/index.cjs +15 -15
  55. package/dist/collaboration/index.js +1 -1
  56. package/dist/collaboration/mail/MailMessageView.d.ts +3 -1
  57. package/dist/connect/docx.cjs +7 -6
  58. package/dist/connect/docx.cjs.map +1 -1
  59. package/dist/connect/docx.js +5 -4
  60. package/dist/connect/docx.js.map +1 -1
  61. package/dist/connect/index.cjs +407 -91
  62. package/dist/connect/index.cjs.map +1 -1
  63. package/dist/connect/index.js +361 -46
  64. package/dist/connect/index.js.map +1 -1
  65. package/dist/connection/mail/ConnectedMailComposer.d.ts +12 -2
  66. package/dist/connection/mail/ConnectedMailMessage.d.ts +19 -0
  67. package/dist/connection/mail/ConnectedMailbox.d.ts +7 -3
  68. package/dist/connection/mail/MailComposerForm.d.ts +12 -2
  69. package/dist/connection/mail/index.d.ts +2 -1
  70. package/dist/connection/mail/types.d.ts +15 -0
  71. package/dist/connection/mail/useWordBody.d.ts +48 -0
  72. package/dist/docx/NativeDocxEditor.d.ts +9 -1
  73. package/dist/docx/index.cjs +51 -50
  74. package/dist/docx/index.cjs.map +1 -1
  75. package/dist/docx/index.js +4 -3
  76. package/dist/docx/index.js.map +1 -1
  77. package/dist/facts/index.cjs +2 -2
  78. package/dist/facts/index.js +2 -2
  79. package/dist/files/index.cjs +2 -2
  80. package/dist/files/index.js +2 -2
  81. package/dist/home/index.cjs +17 -17
  82. package/dist/home/index.js +3 -3
  83. package/dist/index.cjs +442 -442
  84. package/dist/index.js +8 -8
  85. package/dist/record/index.cjs +307 -66
  86. package/dist/record/index.cjs.map +1 -1
  87. package/dist/record/index.d.ts +145 -26
  88. package/dist/record/index.js +301 -67
  89. package/dist/record/index.js.map +1 -1
  90. package/dist/retrieval/index.cjs +1 -1
  91. package/dist/retrieval/index.js +1 -1
  92. package/dist/settings/index.cjs +35 -35
  93. package/dist/settings/index.js +2 -2
  94. package/dist/shell/index.cjs +1 -1
  95. package/dist/shell/index.js +1 -1
  96. package/dist/studio/index.cjs +4 -4
  97. package/dist/studio/index.js +3 -3
  98. package/dist/workflow-canvas/index.cjs +11 -11
  99. package/dist/workflow-canvas/index.js +4 -4
  100. package/dist/workspaces/index.cjs +2 -2
  101. package/dist/workspaces/index.js +2 -2
  102. package/node_modules/@arbidocs/coedit/package.json +1 -1
  103. package/package.json +8 -5
  104. package/record/render-overlays.mjs +152 -0
  105. package/record/render-take.py +985 -0
  106. package/src/docx/styles.css +22 -0
  107. package/dist/chunk-3R6XBYL7.cjs.map +0 -1
  108. package/dist/chunk-4GFB5U3K.js.map +0 -1
  109. package/dist/chunk-7LLLQUM7.cjs.map +0 -1
  110. package/dist/chunk-LGX6UFYG.js.map +0 -1
  111. package/dist/chunk-W2RTZOK3.js.map +0 -1
  112. package/dist/chunk-ZWWN4CUL.cjs.map +0 -1
@@ -3,20 +3,20 @@ import { FactPassageItem } from './chunk-HFZ45FD3.js';
3
3
  import { stylesXml, definitionBookmark, groupDefinitions, definitionLabels, filterTermGroups, esc, hyperlinkRelationship, docxPackage, documentXml, definitionIdOfBookmark, compareTerms, parseDefinition } from './chunk-EPHGD4YV.js';
4
4
  import { filterMentionCandidates, MentionMenu, findMentionQuery, listOptionId, extractMentions, useListKeyNav } from './chunk-AA3PT6KR.js';
5
5
  import { unzipSync, strFromU8, strToU8, zipSync } from 'fflate';
6
- import { collectReviewItems, revisionItemsOf } from '@docx-editor.dev/core/store';
7
- import React4, { createContext, useContext, useState, useRef, useCallback, useEffect, useLayoutEffect, useMemo, useSyncExternalStore } from 'react';
8
- import { useDocxEditor, ReviewRailContext, useEditorSnapshot, useEditorState, useChromeTranslate, DocxEditor, createFontSource, useParagraphStyle, normalizeImageBytes, usePageSetup, useEditorCommand } from '@docx-editor.dev/react';
6
+ import { renderToStaticMarkup } from 'react-dom/server';
9
7
  import { AiMarkdown, Dialog, DialogContent, DialogHeader, DialogTitle, cn, DialogDescription, Button, DialogFooter, Tabs, TabsList, TabsTrigger, TabsContent, Input, splitMarkdownBlocks } from '@arbidocs/react/ui';
10
- import { MessageSquarePlus, MessageSquare, PencilLine, ChevronLeft, ChevronRight, X, ChevronUp, ChevronDown, CheckCheck, RotateCcw, CircleCheck, Trash2, Check, SendHorizontal, Loader2, Sparkles, Type, Pilcrow, UsersRound, RefreshCw, WifiOff, Download, Save, Ellipsis, BookA, Blocks, Link2, Superscript, NotebookText, RefreshCcw, Columns2, Settings2, BetweenHorizontalStart, BetweenHorizontalEnd, BetweenVerticalStart, BetweenVerticalEnd, Merge, Split, Heading, AlignVerticalJustifyStart, AlignVerticalJustifyCenter, AlignVerticalJustifyEnd, RotateCw, FlipHorizontal2, FlipVertical2, Scaling, RectangleVertical, RectangleHorizontal, FileText, SeparatorHorizontal, Captions } from 'lucide-react';
11
8
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
9
+ import React4, { createContext, useContext, useState, useRef, useCallback, useEffect, useLayoutEffect, useMemo, useSyncExternalStore } from 'react';
12
10
  import { createPortal } from 'react-dom';
13
- import { defaultLegalTemplate, listBuildingBlocks, attachedTemplateOf, linkedLevelStyles, styleDivergence, buildingBlockFragment, fragmentClipboardHtml, trackInsertion, newFromTemplate } from '@arbidocs/coedit/template';
14
- import { resolveNumberingLevel, resolveStoryListItems, buildStyleCascadeTable, withNumberingStyleLinks, buildNumberingIndex } from '@docx-editor.dev/core/layout';
15
- import { executeImageCommand } from '@docx-editor.dev/core/editor';
16
- import { renderToStaticMarkup } from 'react-dom/server';
11
+ import { collectReviewItems, revisionItemsOf } from '@docx-editor.dev/core/store';
12
+ import { useDocxEditor, ReviewRailContext, useEditorSnapshot, useEditorState, useChromeTranslate, DocxEditor, useParagraphStyle, normalizeImageBytes, usePageSetup, useEditorCommand } from '@docx-editor.dev/react';
13
+ import { MessageSquarePlus, MessageSquare, PencilLine, ChevronLeft, ChevronRight, X, ChevronUp, ChevronDown, CheckCheck, RotateCcw, CircleCheck, Trash2, Check, SendHorizontal, Loader2, Sparkles, Type, Pilcrow, UsersRound, RefreshCw, WifiOff, Download, Save, Ellipsis, BookA, Blocks, Link2, Superscript, NotebookText, RefreshCcw, Columns2, Settings2, BetweenHorizontalStart, BetweenHorizontalEnd, BetweenVerticalStart, BetweenVerticalEnd, Merge, Split, Heading, AlignVerticalJustifyStart, AlignVerticalJustifyCenter, AlignVerticalJustifyEnd, RotateCw, FlipHorizontal2, FlipVertical2, Scaling, RectangleVertical, RectangleHorizontal, FileText, SeparatorHorizontal, Captions } from 'lucide-react';
17
14
  import * as Y from 'yjs';
18
15
  import { DOCUMENT_COLLABORATION_VERSIONS, collaborationModule } from '@arbidocs/coedit';
19
16
  import { createHocuspocusCollaboration } from '@arbidocs/coedit/hocuspocus';
17
+ import { defaultLegalTemplate, listBuildingBlocks, attachedTemplateOf, linkedLevelStyles, styleDivergence, buildingBlockFragment, fragmentClipboardHtml, trackInsertion, newFromTemplate } from '@arbidocs/coedit/template';
18
+ import { resolveNumberingLevel, resolveStoryListItems, buildStyleCascadeTable, withNumberingStyleLinks, buildNumberingIndex } from '@docx-editor.dev/core/layout';
19
+ import { executeImageCommand } from '@docx-editor.dev/core/editor';
20
20
 
21
21
  function normalizeDocxExport(data) {
22
22
  const files = unzipSync(new Uint8Array(data));
@@ -82,6 +82,93 @@ function normalizeDocxExport(data) {
82
82
  if (!changed) return data;
83
83
  return zipSync(files).slice().buffer;
84
84
  }
85
+ function wordDraftHtml(markdown) {
86
+ const template = document.createElement("template");
87
+ template.innerHTML = renderToStaticMarkup(/* @__PURE__ */ jsx(AiMarkdown, { children: markdown }));
88
+ template.content.querySelectorAll('button, svg, [data-testid="code-block-header"]').forEach((node) => node.remove());
89
+ template.content.querySelectorAll('[data-testid="markdown-code-block"]').forEach((node) => {
90
+ const pre = node.querySelector("pre");
91
+ if (pre) node.replaceWith(pre);
92
+ });
93
+ return template.innerHTML;
94
+ }
95
+ function requireEdit(result) {
96
+ if (!result.ok) throw new Error(result.reason);
97
+ }
98
+ var WordDraftStream = class {
99
+ constructor(editor) {
100
+ this.editor = editor;
101
+ }
102
+ editor;
103
+ committed = [];
104
+ pending;
105
+ resetAfterFailure = false;
106
+ update(content, complete) {
107
+ const blocks = splitMarkdownBlocks(content);
108
+ const completeCount = complete ? blocks.length : Math.max(0, blocks.length - 1);
109
+ requireEdit(this.editor.setEditingMode("editing"));
110
+ try {
111
+ if (this.resetAfterFailure || this.committed.some((block, index) => blocks[index] !== block)) {
112
+ requireEdit(this.editor.exec({ type: "selectAll" }));
113
+ requireEdit(this.editor.exec({ type: "deleteText" }));
114
+ this.committed = [];
115
+ this.pending = void 0;
116
+ this.resetAfterFailure = false;
117
+ }
118
+ for (let index = this.committed.length; index < blocks.length; index++) {
119
+ const tail = this.editor.query({ type: "paragraphs" }).at(-1);
120
+ const start = this.pending?.start || tail?.paraId;
121
+ if (!start) throw new Error("The draft insertion paragraph is unavailable.");
122
+ requireEdit(
123
+ this.editor.exec(
124
+ this.pending ? {
125
+ type: "setSelection",
126
+ range: { from: { paraId: start }, to: { paraId: this.pending.end } }
127
+ } : { type: "setSelection", anchor: { paraId: start } }
128
+ )
129
+ );
130
+ const html = wordDraftHtml(blocks[index]) + "<p></p>";
131
+ const element = document.createElement("div");
132
+ element.innerHTML = html;
133
+ const startIndex = this.editor.query({ type: "paragraphs" }).findIndex((p) => p.paraId === start);
134
+ requireEdit(this.editor.exec({ type: "paste", html, text: element.textContent || "" }));
135
+ const inserted = this.editor.query({ type: "paragraphs" }).slice(startIndex);
136
+ let headingIndex = 0;
137
+ for (const heading of Array.from(element.querySelectorAll("h1,h2,h3,h4,h5,h6"))) {
138
+ const match = inserted.findIndex(
139
+ (p, i) => i >= headingIndex && p.text === heading.textContent
140
+ );
141
+ if (match >= 0 && inserted[match].paraId) {
142
+ requireEdit(
143
+ this.editor.exec({
144
+ type: "setSelection",
145
+ anchor: { paraId: inserted[match].paraId }
146
+ })
147
+ );
148
+ requireEdit(
149
+ this.editor.exec({
150
+ type: "setParagraphStyle",
151
+ styleId: `Heading${heading.tagName.slice(1)}`
152
+ })
153
+ );
154
+ headingIndex = match + 1;
155
+ }
156
+ }
157
+ const end = this.editor.query({ type: "paragraphs" }).at(-1)?.paraId;
158
+ if (!end) throw new Error("The draft continuation paragraph is unavailable.");
159
+ if (index < completeCount) {
160
+ this.committed.push(blocks[index]);
161
+ this.pending = void 0;
162
+ } else this.pending = { start, end };
163
+ }
164
+ } catch (error) {
165
+ this.resetAfterFailure = true;
166
+ throw error;
167
+ } finally {
168
+ requireEdit(this.editor.setEditingMode("viewing"));
169
+ }
170
+ }
171
+ };
85
172
  function findNode(node, id) {
86
173
  if (node.id === id) return node;
87
174
  for (const child of node.children ?? []) {
@@ -105,6 +192,22 @@ function reviewModule() {
105
192
  }
106
193
  };
107
194
  }
195
+
196
+ // src/docx/review/commentText.ts
197
+ var SKIP = /* @__PURE__ */ new Set(["delText", "instrText", "delInstrText", "pPr", "rPr"]);
198
+ function inline(node) {
199
+ if (node.kind === "textValue") return node.value ?? "";
200
+ if (node.localName && SKIP.has(node.localName)) return "";
201
+ if (node.localName === "br" || node.localName === "cr") return "\n";
202
+ if (node.localName === "tab") return " ";
203
+ return (node.children ?? []).map(inline).join("");
204
+ }
205
+ function commentText(entry) {
206
+ if (entry.kind !== "comment") return entry.text;
207
+ const blocks = entry.item?.comment?.blocks ?? [];
208
+ const text = blocks.map(inline).join("\n").trim();
209
+ return text || entry.text;
210
+ }
108
211
  var NONE = [];
109
212
  var ok = (result) => result?.ok === true;
110
213
  function useReview() {
@@ -140,22 +243,6 @@ var ReviewItemContext = createContext(null);
140
243
  function useReviewItem() {
141
244
  return useContext(ReviewItemContext);
142
245
  }
143
-
144
- // src/docx/review/commentText.ts
145
- var SKIP = /* @__PURE__ */ new Set(["delText", "instrText", "delInstrText", "pPr", "rPr"]);
146
- function inline(node) {
147
- if (node.kind === "textValue") return node.value ?? "";
148
- if (node.localName && SKIP.has(node.localName)) return "";
149
- if (node.localName === "br" || node.localName === "cr") return "\n";
150
- if (node.localName === "tab") return " ";
151
- return (node.children ?? []).map(inline).join("");
152
- }
153
- function commentText(entry) {
154
- if (entry.kind !== "comment") return entry.text;
155
- const blocks = entry.item?.comment?.blocks ?? [];
156
- const text = blocks.map(inline).join("\n").trim();
157
- return text || entry.text;
158
- }
159
246
  var CARD_GAP = 8;
160
247
  var CLUSTER_GAP = 24;
161
248
  var REVIEW_MARGIN_MIN_WIDTH = 900;
@@ -1793,6 +1880,8 @@ function structuralPort(editor) {
1793
1880
  }
1794
1881
  };
1795
1882
  }
1883
+
1884
+ // src/docx/structural/useTrackedOrNoted.ts
1796
1885
  var DocxAuthorContext = createContext("You");
1797
1886
  function useTrackedOrNoted() {
1798
1887
  const editor = useDocxEditor();
@@ -2176,1796 +2265,1529 @@ function DefinedTermMenu({
2176
2265
  }
2177
2266
  );
2178
2267
  }
2179
- var BUILTIN_TEMPLATE_NAME = "ARBI legal template";
2180
- var templateFileName = (t) => /\.(dotx|dotm|docx|docm)$/i.test(t.name) ? t.name : `${t.name}.dotx`;
2181
- var builtin;
2182
- function builtinHouseTemplate() {
2183
- builtin ??= { bytes: defaultLegalTemplate(), name: BUILTIN_TEMPLATE_NAME, source: "builtin" };
2184
- return builtin;
2185
- }
2186
- async function resolveHouseTemplate(host) {
2187
- if (!host) return builtinHouseTemplate();
2188
- try {
2189
- return await host.load();
2190
- } catch {
2191
- return builtinHouseTemplate();
2192
- }
2193
- }
2194
- var HouseTemplateContext = createContext(null);
2195
- var useHouseTemplate = () => useContext(HouseTemplateContext);
2196
- function HouseTemplateProvider({
2197
- host,
2198
- documentId,
2199
- editable,
2200
- notify,
2201
- enabled = true,
2202
- children
2268
+ function RibbonMenu({
2269
+ label,
2270
+ icon,
2271
+ children,
2272
+ className,
2273
+ testId,
2274
+ title,
2275
+ keepMounted = false,
2276
+ popoverClassName
2203
2277
  }) {
2204
- const editor = useDocxEditor();
2205
- const track = useTrackedOrNoted();
2206
- const [template, setTemplate] = useState(builtinHouseTemplate);
2207
- const [divergence, setDivergence] = useState(null);
2208
- const [basedOnTemplate, setBasedOnTemplate] = useState(false);
2209
- const [levels, setLevels] = useState({});
2210
- const [generation, setGeneration] = useState(0);
2278
+ const [open, setOpen] = useState(false);
2279
+ const element = useRef(null);
2211
2280
  useEffect(() => {
2212
- let live = true;
2213
- void resolveHouseTemplate(host).then((t) => live && setTemplate(t));
2214
- return () => {
2215
- live = false;
2281
+ if (!open) return;
2282
+ const dismiss = (event) => {
2283
+ if (!element.current?.contains(event.target)) setOpen(false);
2216
2284
  };
2217
- }, [host]);
2218
- const blocks = useMemo(() => {
2219
- try {
2220
- return listBuildingBlocks(template.bytes);
2221
- } catch {
2222
- return [];
2223
- }
2224
- }, [template]);
2225
- useEffect(() => {
2226
- if (!editor || !enabled) return;
2227
- let live = true;
2228
- void editor.save().then((saved) => {
2229
- if (!live || !saved) return;
2230
- const bytes = new Uint8Array(saved);
2231
- setBasedOnTemplate(attachedTemplateOf(bytes) === templateFileName(template));
2232
- setLevels(linkedLevelStyles(bytes));
2233
- setDivergence(styleDivergence(bytes, template.bytes));
2234
- }).catch(() => live && setDivergence(null));
2285
+ const escape = (event) => {
2286
+ if (event.key === "Escape") setOpen(false);
2287
+ };
2288
+ document.addEventListener("pointerdown", dismiss);
2289
+ document.addEventListener("keydown", escape);
2235
2290
  return () => {
2236
- live = false;
2291
+ document.removeEventListener("pointerdown", dismiss);
2292
+ document.removeEventListener("keydown", escape);
2237
2293
  };
2238
- }, [editor, template, generation, enabled]);
2239
- const refresh = useCallback(() => setGeneration((g) => g + 1), []);
2240
- const insertBlock = useCallback(
2241
- (name) => {
2242
- if (!editor || !editable) return false;
2243
- const fragment = buildingBlockFragment(template.bytes, name);
2244
- if (!fragment) return false;
2245
- try {
2246
- const result = track({
2247
- label: `Inserted Quick Part: ${name}`,
2248
- insert: (revision) => editor.exec({
2249
- type: "paste",
2250
- text: "",
2251
- html: fragmentClipboardHtml(
2252
- revision ? trackInsertion(fragment, revision, true) : fragment
2253
- )
2254
- }),
2255
- inserted: (r) => r.ok
2256
- })?.result;
2257
- if (!result?.ok) throw new Error(result?.reason ?? "The block could not be inserted here.");
2258
- refresh();
2259
- return true;
2260
- } catch (failure) {
2261
- notify?.(
2262
- failure instanceof Error ? failure.message : "Could not insert the block.",
2263
- "error"
2264
- );
2265
- return false;
2294
+ }, [open]);
2295
+ return /* @__PURE__ */ jsxs("div", { className: "native-docx-ribbon-menu", ref: element, children: [
2296
+ /* @__PURE__ */ jsx(
2297
+ "button",
2298
+ {
2299
+ type: "button",
2300
+ className: ["native-docx-ribbon-trigger", className].filter(Boolean).join(" "),
2301
+ "aria-label": label,
2302
+ title: title ?? label,
2303
+ "aria-expanded": open,
2304
+ "data-testid": testId,
2305
+ onMouseDown: (event) => event.preventDefault(),
2306
+ onClick: () => setOpen((value) => !value),
2307
+ children: icon
2266
2308
  }
2267
- },
2268
- [editor, editable, template, notify, refresh, track]
2269
- );
2270
- const setAsHouseTemplate = useMemo(
2271
- () => host?.setFromDocument && documentId ? async () => {
2272
- try {
2273
- await host.setFromDocument(documentId);
2274
- setTemplate(await resolveHouseTemplate(host));
2275
- notify?.("This document is now the house template for new documents.", "success");
2276
- } catch (failure) {
2277
- notify?.(
2278
- failure instanceof Error ? failure.message : "Could not set the house template.",
2279
- "error"
2280
- );
2309
+ ),
2310
+ (open || keepMounted) && /* @__PURE__ */ jsx(
2311
+ "div",
2312
+ {
2313
+ className: ["native-docx-ribbon-popover", popoverClassName].filter(Boolean).join(" "),
2314
+ role: "group",
2315
+ "aria-label": label,
2316
+ hidden: !open,
2317
+ onMouseDown: (event) => {
2318
+ if (!(event.target instanceof HTMLInputElement || event.target instanceof HTMLSelectElement))
2319
+ event.preventDefault();
2320
+ },
2321
+ children: typeof children === "function" ? children(() => setOpen(false)) : children
2281
2322
  }
2282
- } : void 0,
2283
- [host, documentId, notify]
2284
- );
2285
- const value = useMemo(
2286
- () => ({
2287
- template,
2288
- blocks,
2289
- divergence,
2290
- basedOnTemplate,
2291
- levels,
2292
- insertBlock,
2293
- refresh,
2294
- setAsHouseTemplate,
2295
- editable
2296
- }),
2297
- [
2298
- template,
2299
- blocks,
2300
- divergence,
2301
- basedOnTemplate,
2302
- levels,
2303
- insertBlock,
2304
- refresh,
2305
- setAsHouseTemplate,
2306
- editable
2307
- ]
2308
- );
2309
- return /* @__PURE__ */ jsx(HouseTemplateContext.Provider, { value: enabled ? value : null, children });
2323
+ )
2324
+ ] });
2310
2325
  }
2311
2326
 
2312
- // src/docx/references/numbering.ts
2313
- var numbers = (level) => !!level && level.numFmt !== "bullet" && level.numFmt !== "none" && (level.lvlText ?? level.markerText ?? "").length > 0;
2314
- function paragraphNumber(order, items, levelOf, paragraphId) {
2315
- const item = items.get(paragraphId);
2316
- if (!item || !numbers(item)) return null;
2317
- const at = order.indexOf(paragraphId);
2318
- const markerAt = (ilvl) => {
2319
- if (ilvl === item.ilvl) return item.markerText;
2320
- for (let i = at - 1; i >= 0; i--) {
2321
- const other = items.get(order[i]);
2322
- if (other && other.numId === item.numId && other.ilvl === ilvl) return other.markerText;
2323
- }
2324
- return null;
2325
- };
2326
- const chain = [];
2327
- const texts = [];
2328
- for (let u = item.ilvl; u >= 0; u--) {
2329
- const level = levelOf(item.numId, u);
2330
- if (!level || !numbers(level)) continue;
2331
- if (u !== item.ilvl && texts.some((t) => t.includes(`%${u + 1}`))) continue;
2332
- chain.push(u);
2333
- texts.push(level.lvlText);
2334
- }
2335
- const value = chain.reverse().map((u) => markerAt(u) ?? "").join("");
2336
- if (!value) return null;
2337
- return value.length > 1 && value.endsWith(".") ? value.slice(0, -1) : value;
2338
- }
2339
- function listTargets(paragraphs, items, levelOf) {
2340
- const order = paragraphs.map((p) => p.id);
2341
- const out = [];
2342
- for (const p of paragraphs) {
2343
- const text = p.text.replace(//g, "").trim();
2344
- if (!text) continue;
2345
- const number = paragraphNumber(order, items, levelOf, p.id);
2346
- const item = items.get(p.id);
2347
- if (number !== null)
2348
- out.push({
2349
- paragraphId: p.id,
2350
- kind: "numbered",
2351
- number,
2352
- marker: item?.markerText ?? "",
2353
- text,
2354
- heading: p.heading,
2355
- level: item?.ilvl ?? 0
2356
- });
2357
- else if (p.heading)
2358
- out.push({
2359
- paragraphId: p.id,
2360
- kind: "heading",
2361
- number: null,
2362
- marker: "",
2363
- text,
2364
- heading: true,
2365
- level: 0
2366
- });
2327
+ // src/docx/overflowPlan.ts
2328
+ function planOverflow(items, available, moreWidth) {
2329
+ const collapsed = /* @__PURE__ */ new Set();
2330
+ let used = items.reduce((sum, item) => sum + item.width, 0);
2331
+ if (used <= available) return collapsed;
2332
+ used += moreWidth;
2333
+ const order = items.map((item, index) => ({ item, index })).sort((a, b) => a.item.priority - b.item.priority || b.index - a.index);
2334
+ for (const { item } of order) {
2335
+ if (used <= available) break;
2336
+ collapsed.add(item.id);
2337
+ used -= item.width;
2367
2338
  }
2368
- return out;
2339
+ return collapsed;
2369
2340
  }
2370
- function filterTargets(targets, query) {
2371
- const q = query.trim().toLowerCase();
2372
- if (!q) return [...targets];
2373
- return targets.filter(
2374
- (t) => (t.number ?? "").toLowerCase().startsWith(q) || t.text.toLowerCase().includes(q)
2341
+ var Overflow = createContext(null);
2342
+ var MORE_WIDTH = 32;
2343
+ var sameSet = (a, b) => a.size === b.size && [...a].every((id) => b.has(id));
2344
+ function RibbonOverflow({ tab, children }) {
2345
+ const row = useRef(null);
2346
+ const items = useRef(/* @__PURE__ */ new Map());
2347
+ const widths = useRef(/* @__PURE__ */ new Map());
2348
+ const [version, setVersion] = useState(0);
2349
+ const [collapsed, setCollapsed] = useState(() => /* @__PURE__ */ new Set());
2350
+ const [slots, setSlots] = useState({});
2351
+ const [menu, setMenu] = useState([]);
2352
+ const register = useCallback((id, item) => {
2353
+ items.current.set(id, item);
2354
+ setVersion((v) => v + 1);
2355
+ return () => {
2356
+ if (items.current.get(id) === item) items.current.delete(id);
2357
+ setVersion((v) => v + 1);
2358
+ };
2359
+ }, []);
2360
+ const plan = useCallback(() => {
2361
+ const element = row.current;
2362
+ if (!element) return;
2363
+ const gap = parseFloat(getComputedStyle(element).columnGap) || 0;
2364
+ const ordered = Array.from(element.querySelectorAll("[data-ribbon-item]")).map((node) => node.dataset.ribbonItem).filter((id) => items.current.has(id));
2365
+ const measured = ordered.map((id) => {
2366
+ const { element: node, priority } = items.current.get(id);
2367
+ if (!node.hidden) {
2368
+ const width = node.getBoundingClientRect().width;
2369
+ widths.current.set(id, width ? width + gap : 0);
2370
+ }
2371
+ return { id, priority, width: widths.current.get(id) ?? 0 };
2372
+ });
2373
+ const next = planOverflow(measured, element.clientWidth, MORE_WIDTH);
2374
+ setCollapsed((current) => sameSet(current, next) ? current : next);
2375
+ const entries = ordered.filter((id) => next.has(id)).map((id) => ({ id, label: items.current.get(id).label }));
2376
+ setMenu(
2377
+ (current) => current.length === entries.length && current.every((entry, i) => entry.id === entries[i].id && entry.label === entries[i].label) ? current : entries
2378
+ );
2379
+ }, []);
2380
+ useLayoutEffect(plan, [plan, version, tab, collapsed]);
2381
+ useLayoutEffect(() => {
2382
+ const element = row.current;
2383
+ if (!element) return;
2384
+ const observer = new ResizeObserver(() => plan());
2385
+ observer.observe(element);
2386
+ for (const item of items.current.values()) observer.observe(item.element);
2387
+ return () => observer.disconnect();
2388
+ }, [plan, version]);
2389
+ const slotRef = useCallback(
2390
+ (id) => (node) => setSlots((current) => {
2391
+ if (node ? current[id] === node : !(id in current)) return current;
2392
+ const next = { ...current };
2393
+ if (node) next[id] = node;
2394
+ else delete next[id];
2395
+ return next;
2396
+ }),
2397
+ []
2375
2398
  );
2399
+ const context = useMemo(() => ({ collapsed, slots, register }), [collapsed, slots, register]);
2400
+ const shown = menu.filter((entry) => collapsed.has(entry.id));
2401
+ return /* @__PURE__ */ jsx(Overflow.Provider, { value: context, children: /* @__PURE__ */ jsxs("div", { className: "native-docx-ribbon-row", ref: row, "data-testid": "docx-ribbon-row", children: [
2402
+ children,
2403
+ shown.length > 0 && /* @__PURE__ */ jsx(
2404
+ RibbonMenu,
2405
+ {
2406
+ label: "More",
2407
+ title: "More commands",
2408
+ icon: /* @__PURE__ */ jsx(Ellipsis, { size: 16 }),
2409
+ testId: "docx-ribbon-more",
2410
+ keepMounted: true,
2411
+ popoverClassName: "native-docx-ribbon-more",
2412
+ children: (close) => /* @__PURE__ */ jsx(MoreRows, { entries: shown, slotRef, close })
2413
+ },
2414
+ tab
2415
+ )
2416
+ ] }) });
2376
2417
  }
2377
-
2378
- // src/docx/references/commands.ts
2379
- var REF_BOOKMARK_PREFIX = "_Ref";
2380
- function newRefBookmark(taken, random = Math.random) {
2381
- for (; ; ) {
2382
- const name = `${REF_BOOKMARK_PREFIX}${String(Math.floor(random() * 9e8) + 1e8)}`;
2383
- if (!taken.has(name)) return name;
2384
- }
2385
- }
2386
- var styled = (style) => style ? `<w:rPr><w:rStyle w:val="${style}"/></w:rPr>` : "";
2387
- var run = (text, style) => `<w:r>${styled(style)}<w:t xml:space="preserve">${esc(text)}</w:t></w:r>`;
2388
- function refFieldXml(instruction, cached) {
2389
- const r = (inner) => `<w:r>${styled(CROSS_REFERENCE_STYLE)}${inner}</w:r>`;
2390
- return `${r('<w:fldChar w:fldCharType="begin"/>')}${r(
2391
- `<w:instrText xml:space="preserve"> ${esc(instruction)} </w:instrText>`
2392
- )}${r('<w:fldChar w:fldCharType="separate"/>')}${run(cached, CROSS_REFERENCE_STYLE)}${r(
2393
- '<w:fldChar w:fldCharType="end"/>'
2394
- )}`;
2418
+ function MoreRows({
2419
+ entries,
2420
+ slotRef,
2421
+ close
2422
+ }) {
2423
+ const refs = useRef(/* @__PURE__ */ new Map());
2424
+ const refFor = (id) => {
2425
+ let ref = refs.current.get(id);
2426
+ if (!ref) refs.current.set(id, ref = slotRef(id));
2427
+ return ref;
2428
+ };
2429
+ const list = useRef(null);
2430
+ const latest = useRef(close);
2431
+ latest.current = close;
2432
+ useEffect(() => {
2433
+ const element = list.current;
2434
+ if (!element) return;
2435
+ const onClick = (event) => {
2436
+ const target = event.target;
2437
+ const row = target.closest(".native-docx-ribbon-more-row");
2438
+ if (!row) return;
2439
+ const control = row.querySelector(".native-docx-ribbon-more-control");
2440
+ if (!control.contains(target)) {
2441
+ control.querySelector("button:not([disabled])")?.click();
2442
+ return;
2443
+ }
2444
+ const button = target.closest("button");
2445
+ if (button && !button.disabled && !control.querySelector(".native-docx-ribbon-popover")?.contains(button) && !button.hasAttribute("aria-expanded") && !button.hasAttribute("aria-haspopup"))
2446
+ latest.current();
2447
+ };
2448
+ element.addEventListener("click", onClick, true);
2449
+ return () => element.removeEventListener("click", onClick, true);
2450
+ }, []);
2451
+ return /* @__PURE__ */ jsx("div", { className: "native-docx-ribbon-more-list", ref: list, children: entries.map((entry) => /* @__PURE__ */ jsxs("div", { className: "native-docx-ribbon-more-row", "data-ribbon-more": entry.id, children: [
2452
+ /* @__PURE__ */ jsx("span", { className: "native-docx-ribbon-more-control", ref: refFor(entry.id) }),
2453
+ /* @__PURE__ */ jsx("span", { className: "native-docx-ribbon-more-label", children: entry.label })
2454
+ ] }, entry.id)) });
2395
2455
  }
2396
- var refTooltip = (value, mode) => mode === "number" ? `Cross-reference to clause ${value}` : `Cross-reference to \u201C${value}\u201D`;
2397
- function crossReferenceXml(bookmark, mode, value) {
2398
- return `<w:hyperlink w:anchor="${esc(bookmark)}" w:tooltip="${esc(
2399
- refTooltip(value, mode)
2400
- )}" w:history="1">${refFieldXml(refInstruction(bookmark, mode), value)}</w:hyperlink>`;
2401
- }
2402
- var refInstruction = (bookmark, mode) => mode === "number" ? `REF ${bookmark} \\r \\h` : `REF ${bookmark} \\h`;
2403
- var refResult = (target, mode) => mode === "number" && target.number ? target.number : target.text;
2404
- var allBookmarks = (port) => new Set(port.paragraphs().flatMap((p) => p.bookmarks));
2405
- function ensureTargetBookmark(port, target, mode) {
2406
- const paragraph = port.paragraphs().find((p) => p.id === target.paragraphId);
2407
- if (!paragraph) throw new Error("That clause is no longer in the document.");
2408
- const existing = paragraph.bookmarks.find((b) => b.startsWith(REF_BOOKMARK_PREFIX));
2409
- if (existing && mode === "number") return existing;
2410
- const name = newRefBookmark(allBookmarks(port));
2411
- const start = `<w:bookmarkStart w:id="0" w:name="${name}"/>`;
2412
- const end = '<w:bookmarkEnd w:id="0"/>';
2413
- if (mode === "number") {
2414
- port.setCaret({ paragraphId: paragraph.id, offset: 0 });
2415
- if (!port.paste(`<w:p>${start}${end}</w:p>`, "", false))
2416
- throw new Error("The cross-reference target could not be marked.");
2417
- return name;
2418
- }
2419
- const runs = port.runsXml(paragraph.id);
2420
- if (!runs) throw new Error("This heading cannot be referred to by its text; use its number.");
2421
- port.deleteRange(
2422
- { paragraphId: paragraph.id, offset: 0 },
2423
- { paragraphId: paragraph.id, offset: paragraph.text.length }
2424
- );
2425
- if (!port.paste(`<w:p>${start}${runs}${end}</w:p>`, paragraph.text, false))
2426
- throw new Error("The cross-reference target could not be marked.");
2427
- return name;
2428
- }
2429
- function pastePoint(port, caret) {
2430
- const paragraph = port.paragraphs().find((p) => p.id === caret.paragraphId);
2431
- if (paragraph?.text[caret.offset - 1] !== FIELD_ATOM) return caret;
2432
- port.setCaret(caret);
2433
- port.typeText(" ");
2434
- return { paragraphId: caret.paragraphId, offset: caret.offset + 1 };
2435
- }
2436
- function markCrossReferenceTarget(port, target, mode) {
2437
- if (mode === "number" && !target.number) mode = "text";
2438
- const bookmark = ensureTargetBookmark(port, target, mode);
2439
- return { bookmark, mode, value: refResult(target, mode) };
2440
- }
2441
- function insertCrossReferenceField(port, { bookmark, mode, value }, caret) {
2442
- caret = pastePoint(port, caret);
2443
- port.setCaret(caret);
2444
- if (!port.paste(`<w:p>${crossReferenceXml(bookmark, mode, value)}</w:p>`, value, false))
2445
- throw new Error("The cross-reference could not be inserted.");
2446
- port.setCaret({ paragraphId: caret.paragraphId, offset: caret.offset + 1 });
2447
- return { bookmark };
2448
- }
2449
- function insertCrossReference(port, target, mode, caret) {
2450
- return insertCrossReferenceField(port, markCrossReferenceTarget(port, target, mode), caret);
2451
- }
2452
- var documentReferenceTooltip = (text, appName = "ARBI") => `Cross-reference: ${text} \u2014 opens in ${appName}`;
2453
- function insertDocumentReference(port, reference, caret) {
2454
- const text = reference.text.trim();
2455
- if (!text) throw new Error("Enter the reference text.");
2456
- const tooltip = documentReferenceTooltip(text, reference.appName).slice(0, 255);
2457
- caret = pastePoint(port, caret);
2458
- port.setCaret(caret);
2459
- const link = `<w:p><w:hyperlink r:id="rIdRef1"${tooltip ? ` w:tooltip="${esc(tooltip)}"` : ""} w:history="1">${run(text, CROSS_REFERENCE_STYLE)}</w:hyperlink></w:p>`;
2460
- if (!port.paste(link, text, false, hyperlinkRelationship("rIdRef1", reference.url)))
2461
- throw new Error("The reference could not be inserted.");
2462
- port.setCaret({ paragraphId: caret.paragraphId, offset: caret.offset + text.length });
2463
- }
2464
- var REF_FIELD = /^REF\s+(\S+)((?:\s+\\[A-Za-z*]+(?:\s+MERGEFORMAT)?)*)\s*$/i;
2465
- function staleCrossReferences(port) {
2466
- const paragraphs = port.paragraphs();
2467
- if (!paragraphs.some((p) => p.fields?.length)) return [];
2468
- const items = port.listItems();
2469
- const order = paragraphs.map((p) => p.id);
2470
- const owner = /* @__PURE__ */ new Map();
2471
- for (const p of paragraphs) for (const b of p.bookmarks) if (!owner.has(b)) owner.set(b, p.id);
2472
- const out = [];
2473
- for (const p of paragraphs)
2474
- for (const field of p.fields ?? []) {
2475
- const match = REF_FIELD.exec(field.instruction);
2476
- if (!match || !match[1].startsWith(REF_BOOKMARK_PREFIX)) continue;
2477
- const switches = match[2].toLowerCase();
2478
- const mode = /\\[rwn]\b/.test(switches) ? "number" : "text";
2479
- const target = owner.get(match[1]);
2480
- const base = {
2481
- paragraphId: p.id,
2482
- offset: field.offset,
2483
- instruction: field.instruction,
2484
- cached: field.result,
2485
- bookmark: match[1],
2486
- mode
2487
- };
2488
- if (!target) {
2489
- out.push({ ...base, value: null });
2490
- continue;
2491
- }
2492
- const value = /\\[rwn]\b/.test(switches) ? switches.includes("\\r") ? paragraphNumber(order, items, port.levelOf, target) : null : (paragraphs.find((x) => x.id === target)?.text ?? "").replace(//g, "").trim();
2493
- if (value && value !== field.result.trim()) out.push({ ...base, value });
2456
+ function RibbonItem({
2457
+ id,
2458
+ label,
2459
+ priority = 0,
2460
+ children
2461
+ }) {
2462
+ const context = useContext(Overflow);
2463
+ const element = useRef(null);
2464
+ const register = context?.register;
2465
+ useLayoutEffect(() => {
2466
+ if (!register || !element.current) return;
2467
+ return register(id, { label, priority, element: element.current });
2468
+ }, [register, id, label, priority]);
2469
+ if (!context) return /* @__PURE__ */ jsx(Fragment, { children });
2470
+ const collapsed = context.collapsed.has(id);
2471
+ const slot = context.slots[id];
2472
+ return /* @__PURE__ */ jsx(
2473
+ "span",
2474
+ {
2475
+ ref: element,
2476
+ className: "native-docx-ribbon-item",
2477
+ "data-ribbon-item": id,
2478
+ hidden: collapsed || void 0,
2479
+ children: collapsed ? slot ? createPortal(children, slot) : null : children
2494
2480
  }
2495
- return out;
2496
- }
2497
- function brokenCrossReferences(port) {
2498
- return port.paragraphs().flatMap(
2499
- (p) => (p.marks ?? []).filter((m) => m.styleId === BROKEN_CROSS_REFERENCE_STYLE).map((m) => ({ paragraphId: p.id, offset: m.offset, text: m.text }))
2500
- );
2501
- }
2502
- function refreshCrossReferences(port) {
2503
- const stale = staleCrossReferences(port).sort(
2504
- (a, b) => a.paragraphId === b.paragraphId ? b.offset - a.offset : 0
2505
2481
  );
2506
- let refreshed = 0;
2507
- for (const ref of stale) {
2508
- const paragraph = port.paragraphs().find((p) => p.id === ref.paragraphId);
2509
- if (!paragraph || paragraph.text[ref.offset - 1] === FIELD_ATOM) continue;
2510
- const at = { paragraphId: ref.paragraphId, offset: ref.offset };
2511
- port.deleteRange(at, { paragraphId: ref.paragraphId, offset: ref.offset + 1 });
2512
- port.setCaret(at);
2513
- const text = ref.value ?? (ref.cached.trim() || ref.bookmark);
2514
- const body = ref.value === null ? run(text, BROKEN_CROSS_REFERENCE_STYLE) : crossReferenceXml(ref.bookmark, ref.mode, ref.value);
2515
- if (port.paste(`<w:p>${body}</w:p>`, text, false) && ref.value !== null) refreshed++;
2516
- }
2517
- return { refreshed, broken: brokenCrossReferences(port) };
2518
2482
  }
2519
2483
 
2520
- // src/docx/references/passages.ts
2521
- function shortTitle(title) {
2522
- const base = title.replace(/\.[A-Za-z0-9]{1,5}$/, "").trim();
2523
- const defined = /\(\s*(?:the\s+)?["“'‘]([^"”'’]{1,30})["”'’]\s*\)/i.exec(base);
2524
- return defined ? defined[1] : base;
2525
- }
2526
- function passageClause(text) {
2527
- const first = text.trim().split("\n")[0] ?? "";
2528
- const match = /^(?:#+\s*)?(?:\*\*)?(?:clause|section|article)?\s*(\d+(?:\.\d+)*)[.)]?(?:\*\*)?\s+\S/i.exec(
2529
- first
2530
- );
2531
- return match ? match[1] : null;
2532
- }
2533
- function referenceText(title, passage) {
2534
- const name = shortTitle(title);
2535
- const clause = passageClause(passage.text);
2536
- if (clause) return `${name}, clause ${clause}`;
2537
- if (passage.page) return `${name}, p. ${passage.page}`;
2538
- return name;
2484
+ // src/docx/wordStyles.ts
2485
+ var QUICK_STYLES = ["Normal", "Title", "heading 1", "heading 2", "heading 3"];
2486
+ function findStyle(options, name) {
2487
+ const wanted = name.toLowerCase();
2488
+ const id = name.replace(/(^|\s)(\w)/g, (_, _s, c) => c.toUpperCase());
2489
+ return options.find((o) => o.name.toLowerCase() === wanted) ?? options.find((o) => o.styleId.toLowerCase() === id.toLowerCase());
2539
2490
  }
2540
- function passageExcerpt(text, max = 280) {
2541
- const plain = text.replace(/[#*_`>|]+/g, " ").replace(/\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/\s+/g, " ").trim();
2542
- return plain.length > max ? `${plain.slice(0, max - 1)}\u2026` : plain;
2491
+ function styleLabel(name) {
2492
+ return name === name.toLowerCase() ? name.replace(/(^|\s)(\w)/g, (m) => m.toUpperCase()) : name;
2543
2493
  }
2544
- function filterPassages(passages, query) {
2545
- const q = query.trim().toLowerCase();
2546
- return q ? passages.filter((p) => p.text.toLowerCase().includes(q)) : [...passages];
2494
+ function wordShortcut(event) {
2495
+ const mod = event.ctrlKey || event.metaKey;
2496
+ if (!mod) return null;
2497
+ if (event.altKey && !event.shiftKey) {
2498
+ switch (event.code) {
2499
+ case "KeyF":
2500
+ return "footnote";
2501
+ case "KeyD":
2502
+ return "endnote";
2503
+ case "Digit1":
2504
+ case "Numpad1":
2505
+ return "heading 1";
2506
+ case "Digit2":
2507
+ case "Numpad2":
2508
+ return "heading 2";
2509
+ case "Digit3":
2510
+ case "Numpad3":
2511
+ return "heading 3";
2512
+ }
2513
+ }
2514
+ if (event.shiftKey && !event.altKey && event.code === "KeyN") return "Normal";
2515
+ return null;
2547
2516
  }
2548
- var RUN_PARTS = /* @__PURE__ */ new Set(["rPr", "t", "tab", "br", "lastRenderedPageBreak"]);
2549
- var SKIPPED = /* @__PURE__ */ new Set(["pPr", "bookmarkStart", "bookmarkEnd", "proofErr", "permStart", "permEnd"]);
2550
- function serialize(node) {
2551
- if (node.kind === "textValue") return esc(node.value ?? "");
2552
- const name = `${node.prefix ?? "w"}:${node.localName}`;
2553
- const attrs = (node.attributes ?? []).map((a) => ` ${a.prefix ? `${a.prefix}:` : ""}${a.localName}="${esc(a.value)}"`).join("");
2554
- const children = (node.children ?? []).map((c) => serialize(c)).join("");
2555
- return children ? `<${name}${attrs}>${children}</${name}>` : `<${name}${attrs}/>`;
2517
+ function divergenceSummary(divergence) {
2518
+ if (!divergence) return null;
2519
+ const changed = divergence.styles.filter((s) => s.kind === "changed").map((s) => styleLabel(s.name));
2520
+ const missing = divergence.styles.filter((s) => s.kind === "missing").map((s) => styleLabel(s.name));
2521
+ const parts = [
2522
+ divergence.docDefaults ? "default text formatting" : "",
2523
+ changed.length ? `${changed.length === 1 ? "style" : "styles"} ${changed.slice(0, 6).join(", ")}${changed.length > 6 ? "\u2026" : ""}` : "",
2524
+ missing.length ? `${missing.length} house ${missing.length === 1 ? "style" : "styles"} missing` : ""
2525
+ ].filter(Boolean);
2526
+ return parts.length ? `Differs from the house template: ${parts.join("; ")}.` : null;
2556
2527
  }
2557
- function paragraphRunsXml(paragraph) {
2558
- const runs = [];
2559
- for (const child of paragraph.children ?? []) {
2560
- if (child.localName && SKIPPED.has(child.localName)) continue;
2561
- if (child.localName !== "r") return null;
2562
- if ((child.children ?? []).some((c) => !c.localName || !RUN_PARTS.has(c.localName))) return null;
2563
- runs.push(serialize(child));
2564
- }
2565
- return runs.length ? runs.join("") : null;
2528
+ var BUILTIN_TEMPLATE_NAME = "ARBI legal template";
2529
+ var templateFileName = (t) => /\.(dotx|dotm|docx|docm)$/i.test(t.name) ? t.name : `${t.name}.dotx`;
2530
+ var builtin;
2531
+ function builtinHouseTemplate() {
2532
+ builtin ??= { bytes: defaultLegalTemplate(), name: BUILTIN_TEMPLATE_NAME, source: "builtin" };
2533
+ return builtin;
2566
2534
  }
2567
- function findNode2(node, id) {
2568
- if (node.id === id) return node;
2569
- for (const child of node.children ?? []) {
2570
- const found = findNode2(child, id);
2571
- if (found) return found;
2535
+ async function resolveHouseTemplate(host) {
2536
+ if (!host) return builtinHouseTemplate();
2537
+ try {
2538
+ return await host.load();
2539
+ } catch {
2540
+ return builtinHouseTemplate();
2572
2541
  }
2573
- return void 0;
2574
2542
  }
2575
- function referencesPort(editor, revision) {
2576
- const base = editorPort(editor, revision);
2577
- const session = () => {
2578
- const s = editor.surface?.session;
2579
- if (!s) throw new Error("The document is not open.");
2580
- return s;
2581
- };
2582
- const root = () => session().part().root;
2583
- let numbering;
2584
- let cascade;
2585
- const index = () => {
2586
- if (!numbering) {
2587
- const s = session();
2588
- cascade = buildStyleCascadeTable(s.stylesRoot(), void 0, s.settingsRoot());
2589
- numbering = withNumberingStyleLinks(buildNumberingIndex(s.numberingRoot()), cascade);
2590
- }
2591
- return numbering;
2592
- };
2593
- return {
2594
- ...base,
2595
- listItems: () => {
2596
- const body = root().children?.find((n) => n.kind === "body");
2597
- const items = resolveStoryListItems(
2598
- body?.children ?? [],
2599
- index(),
2600
- cascade
2601
- );
2602
- const out = /* @__PURE__ */ new Map();
2603
- for (const [id, item] of items)
2604
- out.set(id, {
2605
- numId: item.numId,
2606
- ilvl: item.ilvl,
2607
- markerText: item.markerText,
2608
- numFmt: item.numFmt
2609
- });
2610
- return out;
2611
- },
2612
- levelOf: (numId, ilvl) => {
2613
- const level = resolveNumberingLevel(index(), numId, ilvl)?.level;
2614
- return level ? { lvlText: level.lvlText, numFmt: level.numFmt } : null;
2615
- },
2616
- runsXml: (paragraphId) => {
2617
- const node = findNode2(root(), paragraphId);
2618
- return node ? paragraphRunsXml(node) : null;
2619
- }
2620
- };
2621
- }
2622
-
2623
- // src/docx/beforeSave.ts
2624
- var hooks = /* @__PURE__ */ new WeakMap();
2625
- function onBeforeSave(editor, fn) {
2626
- let set = hooks.get(editor);
2627
- if (!set) hooks.set(editor, set = /* @__PURE__ */ new Set());
2628
- set.add(fn);
2629
- return () => set.delete(fn);
2630
- }
2631
- function runBeforeSave(editor) {
2632
- if (!editor) return;
2633
- for (const fn of hooks.get(editor) ?? [])
2634
- try {
2635
- fn();
2636
- } catch {
2637
- }
2638
- }
2639
- var DEFAULT_CROSS_REFERENCES_LABELS = {
2640
- insert: "Insert cross-reference",
2641
- title: "Insert cross-reference",
2642
- description: "Refer to a clause of this document, or to a passage of another document.",
2643
- thisDocument: "This document",
2644
- otherDocument: "Another document",
2645
- filterTargets: "Find a clause or heading",
2646
- noTargets: "This document has no numbered clauses or headings yet.",
2647
- number: "Paragraph number",
2648
- text: "Heading text",
2649
- insertAs: "Insert",
2650
- chooseDocument: "Choose a document",
2651
- filterPassages: "Find a passage",
2652
- loadingPassages: "Loading passages\u2026",
2653
- noPassages: "No passage matches.",
2654
- referenceText: "Reference text",
2655
- insertButton: "Insert",
2656
- cancel: "Cancel",
2657
- clause: (number) => `Clause ${number}`,
2658
- heading: "Heading",
2659
- update: "Update cross-references",
2660
- updated: "Cross-references updated",
2661
- updateSummary: (refreshed) => refreshed ? `${refreshed} cross-reference${refreshed === 1 ? "" : "s"} brought up to date.` : "Every cross-reference is up to date.",
2662
- brokenTitle: "Broken cross-references",
2663
- brokenNone: "No cross-reference points at a deleted target.",
2664
- brokenHelp: "Their target was deleted: the last text is kept and marked. Insert the reference again to repair it.",
2665
- brokenCard: "Broken cross-reference",
2666
- brokenCardBody: "Its target was deleted. Update cross-references to mark it.",
2667
- show: "Show",
2668
- close: "Close"
2669
- };
2670
- var CrossReferencesContext = createContext(null);
2671
- var useCrossReferences = () => useContext(CrossReferencesContext);
2672
- function CrossReferencesProvider({
2673
- children,
2674
- editable,
2675
- references,
2543
+ var HouseTemplateContext = createContext(null);
2544
+ var useHouseTemplate = () => useContext(HouseTemplateContext);
2545
+ function HouseTemplateProvider({
2546
+ host,
2676
2547
  documentId,
2548
+ editable,
2677
2549
  notify,
2678
- labels
2550
+ enabled = true,
2551
+ children
2679
2552
  }) {
2680
2553
  const editor = useDocxEditor();
2681
2554
  const track = useTrackedOrNoted();
2682
- const l = useMemo(() => ({ ...DEFAULT_CROSS_REFERENCES_LABELS, ...labels }), [labels]);
2683
- const [dialog, setDialog] = useState(
2684
- null
2685
- );
2686
- const marker = useRef(null);
2687
- const [viewer, setViewer] = useState(null);
2688
- useEffect(() => setViewer(marker.current?.closest(".native-docx-viewer") ?? null), []);
2689
- const openDialog = useCallback(() => {
2690
- if (!editor || !editable) return;
2555
+ const [template, setTemplate] = useState(builtinHouseTemplate);
2556
+ const [divergence, setDivergence] = useState(null);
2557
+ const [basedOnTemplate, setBasedOnTemplate] = useState(false);
2558
+ const [levels, setLevels] = useState({});
2559
+ const [generation, setGeneration] = useState(0);
2560
+ useEffect(() => {
2561
+ let live = true;
2562
+ void resolveHouseTemplate(host).then((t) => live && setTemplate(t));
2563
+ return () => {
2564
+ live = false;
2565
+ };
2566
+ }, [host]);
2567
+ const blocks = useMemo(() => {
2691
2568
  try {
2692
- const port = referencesPort(editor);
2693
- const caret = port.caret();
2694
- if (!caret) return notify?.("Place the cursor where the reference should go.", "error");
2695
- setDialog({ caret, targets: listTargets(port.paragraphs(), port.listItems(), port.levelOf) });
2696
- } catch (error) {
2697
- notify?.(error instanceof Error ? error.message : "Could not read the document.", "error");
2569
+ return listBuildingBlocks(template.bytes);
2570
+ } catch {
2571
+ return [];
2698
2572
  }
2699
- }, [editor, editable, notify]);
2700
- const run2 = useCallback(
2701
- (label, prepare, insert) => {
2702
- if (!editor) return;
2573
+ }, [template]);
2574
+ useEffect(() => {
2575
+ if (!editor || !enabled) return;
2576
+ let live = true;
2577
+ void editor.save().then((saved) => {
2578
+ if (!live || !saved) return;
2579
+ const bytes = new Uint8Array(saved);
2580
+ setBasedOnTemplate(attachedTemplateOf(bytes) === templateFileName(template));
2581
+ setLevels(linkedLevelStyles(bytes));
2582
+ setDivergence(styleDivergence(bytes, template.bytes));
2583
+ }).catch(() => live && setDivergence(null));
2584
+ return () => {
2585
+ live = false;
2586
+ };
2587
+ }, [editor, template, generation, enabled]);
2588
+ const refresh = useCallback(() => setGeneration((g) => g + 1), []);
2589
+ const insertBlock = useCallback(
2590
+ (name) => {
2591
+ if (!editor || !editable) return false;
2592
+ const fragment = buildingBlockFragment(template.bytes, name);
2593
+ if (!fragment) return false;
2703
2594
  try {
2704
- const prepared = withDirectEditing(editor, prepare);
2705
- if (dialog) referencesPort(editor).setCaret(dialog.caret);
2706
- track({ label, insert: (revision) => insert(prepared, revision), inserted: () => true });
2707
- setDialog(null);
2708
- editor.surface?.focus();
2709
- holdCaret(editor);
2710
- } catch (error) {
2595
+ const result = track({
2596
+ label: `Inserted Quick Part: ${name}`,
2597
+ insert: (revision) => editor.exec({
2598
+ type: "paste",
2599
+ text: "",
2600
+ html: fragmentClipboardHtml(
2601
+ revision ? trackInsertion(fragment, revision, true) : fragment
2602
+ )
2603
+ }),
2604
+ inserted: (r) => r.ok
2605
+ })?.result;
2606
+ if (!result?.ok) throw new Error(result?.reason ?? "The block could not be inserted here.");
2607
+ refresh();
2608
+ return true;
2609
+ } catch (failure) {
2711
2610
  notify?.(
2712
- error instanceof Error ? error.message : "Could not insert the reference.",
2611
+ failure instanceof Error ? failure.message : "Could not insert the block.",
2713
2612
  "error"
2714
2613
  );
2614
+ return false;
2715
2615
  }
2716
2616
  },
2717
- [editor, dialog, track, notify]
2617
+ [editor, editable, template, notify, refresh, track]
2718
2618
  );
2719
- useEffect(() => {
2720
- if (!editor || !editable) return;
2721
- const refresh = () => {
2619
+ const setAsHouseTemplate = useMemo(
2620
+ () => host?.setFromDocument && documentId ? async () => {
2722
2621
  try {
2723
- const port = referencesPort(editor);
2724
- if (!port.paragraphs().some((p) => p.fields?.length)) return;
2725
- preservingCaret(editor, () => withDirectEditing(editor, () => refreshCrossReferences(port)));
2726
- } catch {
2622
+ await host.setFromDocument(documentId);
2623
+ setTemplate(await resolveHouseTemplate(host));
2624
+ notify?.("This document is now the house template for new documents.", "success");
2625
+ } catch (failure) {
2626
+ notify?.(
2627
+ failure instanceof Error ? failure.message : "Could not set the house template.",
2628
+ "error"
2629
+ );
2727
2630
  }
2728
- };
2729
- const off = onBeforeSave(editor, refresh);
2730
- const timer = setTimeout(refresh, 1500);
2731
- return () => {
2732
- off();
2733
- clearTimeout(timer);
2734
- };
2735
- }, [editor, editable]);
2736
- const [report, setReport] = useState(null);
2737
- const update = useCallback(() => {
2738
- if (!editor || !editable) return;
2739
- try {
2740
- const result = preservingCaret(
2741
- editor,
2742
- () => withDirectEditing(editor, () => refreshCrossReferences(referencesPort(editor)))
2743
- );
2744
- setReport(result);
2745
- } catch (error) {
2746
- notify?.(
2747
- error instanceof Error ? error.message : "Could not update cross-references.",
2748
- "error"
2749
- );
2750
- }
2751
- }, [editor, editable, notify]);
2752
- const [passages, setPassages] = useState({});
2753
- const loadPassages = useCallback(
2754
- (id) => {
2755
- if (!references || passages[id]) return;
2756
- setPassages((p) => ({ ...p, [id]: "loading" }));
2757
- references.passages(id).then((list) => setPassages((p) => ({ ...p, [id]: list }))).catch(() => setPassages((p) => ({ ...p, [id]: [] })));
2758
- },
2759
- [references, passages]
2631
+ } : void 0,
2632
+ [host, documentId, notify]
2760
2633
  );
2761
- useEffect(() => {
2762
- if (!viewer || !references) return;
2763
- const targetOf = (event) => {
2764
- const link = event.target?.closest?.(
2765
- 'a.docx-hyperlink[data-docx-link-kind="external"]'
2766
- );
2767
- if (!link || !viewer.contains(link)) return null;
2768
- const mouse = event;
2769
- if (mouse.button !== 0 || mouse.ctrlKey || mouse.metaKey || mouse.shiftKey) return null;
2770
- return references.targetOf(link.getAttribute("href") ?? "");
2771
- };
2772
- const take = (event) => {
2773
- const target = targetOf(event);
2774
- if (!target) return;
2775
- event.preventDefault();
2776
- event.stopPropagation();
2777
- if (event.type === "click") references.open(target);
2778
- };
2779
- const types = ["pointerdown", "mousedown", "pointerup", "mouseup", "click"];
2780
- for (const type of types) window.addEventListener(type, take, true);
2781
- return () => {
2782
- for (const type of types) window.removeEventListener(type, take, true);
2783
- };
2784
- }, [viewer, references]);
2785
2634
  const value = useMemo(
2786
- () => editable ? { openDialog, update, labels: l } : null,
2787
- [editable, openDialog, update, l]
2635
+ () => ({
2636
+ template,
2637
+ blocks,
2638
+ divergence,
2639
+ basedOnTemplate,
2640
+ levels,
2641
+ insertBlock,
2642
+ refresh,
2643
+ setAsHouseTemplate,
2644
+ editable
2645
+ }),
2646
+ [
2647
+ template,
2648
+ blocks,
2649
+ divergence,
2650
+ basedOnTemplate,
2651
+ levels,
2652
+ insertBlock,
2653
+ refresh,
2654
+ setAsHouseTemplate,
2655
+ editable
2656
+ ]
2788
2657
  );
2789
- const resolveInternal = (href) => {
2790
- if (!editor) return null;
2791
- const name = href.replace(/^#/, "");
2792
- try {
2793
- const port = referencesPort(editor);
2794
- const paragraphs = port.paragraphs();
2795
- const target = paragraphs.find((p) => p.bookmarks.includes(name));
2796
- if (!target) return { title: l.brokenCard, body: l.brokenCardBody };
2797
- const number = paragraphNumber(
2798
- paragraphs.map((p) => p.id),
2799
- port.listItems(),
2800
- port.levelOf,
2801
- target.id
2802
- );
2803
- return {
2804
- title: number ? l.clause(number) : l.heading,
2805
- body: passageExcerpt(target.text.replace(//g, ""))
2806
- };
2807
- } catch {
2808
- return null;
2809
- }
2658
+ return /* @__PURE__ */ jsx(HouseTemplateContext.Provider, { value: enabled ? value : null, children });
2659
+ }
2660
+ var FILTER_FROM = 8;
2661
+ function previewStyle(option) {
2662
+ const { fontFamily, fontSizePt, bold, italic, color } = option.preview;
2663
+ return {
2664
+ fontFamily: fontFamily ? `"${fontFamily}", serif` : void 0,
2665
+ // In the face of the style, but within a row: 9-18pt.
2666
+ fontSize: fontSizePt ? `${Math.min(18, Math.max(9, fontSizePt))}pt` : void 0,
2667
+ fontWeight: bold ? 700 : void 0,
2668
+ fontStyle: italic ? "italic" : void 0,
2669
+ color: color ? `#${color}` : void 0
2810
2670
  };
2811
- return /* @__PURE__ */ jsxs(CrossReferencesContext.Provider, { value, children: [
2812
- /* @__PURE__ */ jsx("span", { ref: marker, hidden: true }),
2813
- children,
2814
- /* @__PURE__ */ jsx(
2815
- LinkHoverCard,
2816
- {
2817
- root: viewer,
2818
- matches: (href) => href.startsWith(`#${REF_BOOKMARK_PREFIX}`),
2819
- resolve: resolveInternal,
2820
- testId: "docx-cross-reference-card"
2821
- }
2671
+ }
2672
+ function Gallery({ close }) {
2673
+ const { value, setValue, options, isEnabled } = useParagraphStyle();
2674
+ const editor = useDocxEditor();
2675
+ const house = useHouseTemplate();
2676
+ const changed = new Set(
2677
+ house?.basedOnTemplate ? house.divergence?.styles.filter((s) => s.kind === "changed").map((s) => s.styleId) : []
2678
+ );
2679
+ const [query, setQuery] = useState("");
2680
+ const quick = useMemo(
2681
+ () => QUICK_STYLES.map((name) => findStyle(options, name)).filter(
2682
+ (o) => !!o
2822
2683
  ),
2823
- references && /* @__PURE__ */ jsx(
2824
- LinkHoverCard,
2684
+ [options]
2685
+ );
2686
+ const shown = useMemo(() => {
2687
+ const q = query.trim().toLowerCase();
2688
+ return q ? options.filter(
2689
+ (o) => o.name.toLowerCase().includes(q) || o.styleId.toLowerCase().includes(q)
2690
+ ) : options;
2691
+ }, [options, query]);
2692
+ const apply = (option) => {
2693
+ setValue(option.styleId);
2694
+ close();
2695
+ editor?.surface?.focus();
2696
+ };
2697
+ const nav = useListKeyNav({
2698
+ open: true,
2699
+ items: shown,
2700
+ onPick: apply,
2701
+ onDismiss: close,
2702
+ resetKey: query
2703
+ });
2704
+ return /* @__PURE__ */ jsxs("div", { className: "native-docx-style-gallery", "data-testid": "docx-style-gallery", children: [
2705
+ quick.length > 0 && /* @__PURE__ */ jsx("div", { className: "native-docx-style-quick", role: "group", "aria-label": "Quick styles", children: quick.map((option) => /* @__PURE__ */ jsx(
2706
+ "button",
2825
2707
  {
2826
- root: viewer,
2827
- matches: (href) => !!references.targetOf(href),
2828
- resolve: (href) => {
2829
- const target = references.targetOf(href);
2830
- if (!target) return null;
2831
- const list = passages[target.documentId];
2832
- if (!list) loadPassages(target.documentId);
2833
- const passage = Array.isArray(list) ? list.find((p) => p.chunkId === target.chunkId) : void 0;
2834
- return passage ? {
2835
- body: passageExcerpt(passage.text),
2836
- footer: passage.page ? `p. ${passage.page}` : void 0
2837
- } : null;
2838
- },
2839
- testId: "docx-cross-reference-card"
2840
- }
2841
- ),
2842
- dialog && /* @__PURE__ */ jsx(
2843
- CrossReferenceDialog,
2708
+ type: "button",
2709
+ "aria-pressed": value === option.styleId,
2710
+ disabled: !isEnabled,
2711
+ "data-style-id": option.styleId,
2712
+ title: styleLabel(option.name),
2713
+ onClick: () => apply(option),
2714
+ children: /* @__PURE__ */ jsx("span", { style: previewStyle(option), children: styleLabel(option.name) })
2715
+ },
2716
+ option.styleId
2717
+ )) }),
2718
+ options.length > FILTER_FROM && /* @__PURE__ */ jsx(
2719
+ "input",
2844
2720
  {
2845
- targets: dialog.targets,
2846
- references,
2847
- documentId,
2848
- passages,
2849
- loadPassages,
2850
- labels: l,
2851
- onCancel: () => setDialog(null),
2852
- onClosed: () => editor?.surface?.focus(),
2853
- onInsertTarget: (target, mode) => run2(
2854
- `Inserted cross-reference: ${refResult(target, mode)}`,
2855
- () => markCrossReferenceTarget(referencesPort(editor), target, mode),
2856
- (marked, revision) => insertCrossReferenceField(referencesPort(editor, revision), marked, dialog.caret)
2857
- ),
2858
- onInsertPassage: (target, text) => run2(
2859
- `Inserted cross-reference: ${text}`,
2860
- () => void 0,
2861
- (_, revision) => insertDocumentReference(
2862
- referencesPort(editor, revision),
2863
- { text, url: references.linkFor(target), appName: references.appName },
2864
- dialog.caret
2865
- )
2866
- )
2721
+ className: "native-docx-style-filter",
2722
+ placeholder: "Find a style",
2723
+ "aria-label": "Find a style",
2724
+ value: query,
2725
+ autoFocus: true,
2726
+ onChange: (event) => setQuery(event.target.value),
2727
+ onKeyDown: (event) => {
2728
+ nav.handleKeyDown(event);
2729
+ },
2730
+ ...nav.comboboxProps
2867
2731
  }
2868
2732
  ),
2869
- /* @__PURE__ */ jsx(Dialog, { open: !!report, onOpenChange: (open) => !open && setReport(null), children: /* @__PURE__ */ jsxs(
2870
- DialogContent,
2733
+ /* @__PURE__ */ jsxs(
2734
+ "ul",
2871
2735
  {
2872
- className: "max-h-[85dvh] overflow-y-auto",
2873
- "data-testid": "docx-cross-references-report",
2736
+ className: "native-docx-style-list",
2737
+ role: "listbox",
2738
+ id: nav.listboxId,
2739
+ "aria-label": "Document styles",
2874
2740
  children: [
2875
- /* @__PURE__ */ jsxs(DialogHeader, { children: [
2876
- /* @__PURE__ */ jsx(DialogTitle, { children: l.updated }),
2877
- /* @__PURE__ */ jsx(DialogDescription, { children: report && l.updateSummary(report.refreshed) })
2878
- ] }),
2879
- report && /* @__PURE__ */ jsxs("section", { className: "space-y-1 text-sm", children: [
2880
- /* @__PURE__ */ jsx("h3", { className: "font-medium", children: l.brokenTitle }),
2881
- report.broken.length ? /* @__PURE__ */ jsxs(Fragment, { children: [
2882
- /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: l.brokenHelp }),
2883
- /* @__PURE__ */ jsx("ul", { className: "space-y-1", "data-testid": "docx-cross-references-broken", children: report.broken.map((b) => /* @__PURE__ */ jsxs(
2884
- "li",
2885
- {
2886
- className: "flex items-center justify-between gap-2",
2887
- children: [
2888
- /* @__PURE__ */ jsx("span", { className: "text-red-text", children: b.text }),
2889
- /* @__PURE__ */ jsx(
2890
- Button,
2891
- {
2892
- size: "sm",
2893
- variant: "ghost",
2894
- onClick: () => {
2895
- const point = { paragraphId: b.paragraphId, offset: b.offset };
2896
- editor?.surface?.setSelection({ anchor: point, head: point });
2897
- editor?.surface?.revealParagraph(b.paragraphId);
2898
- setReport(null);
2899
- },
2900
- children: l.show
2901
- }
2902
- )
2903
- ]
2904
- },
2905
- `${b.paragraphId}:${b.offset}`
2906
- )) })
2907
- ] }) : /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: l.brokenNone })
2908
- ] }),
2909
- /* @__PURE__ */ jsx(DialogFooter, { children: /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => setReport(null), children: l.close }) })
2741
+ shown.map((option, index) => /* @__PURE__ */ jsxs(
2742
+ "li",
2743
+ {
2744
+ id: listOptionId(nav.listboxId, index),
2745
+ role: "option",
2746
+ "aria-selected": value === option.styleId,
2747
+ "aria-disabled": !isEnabled,
2748
+ "data-active": index === nav.selectedIndex || void 0,
2749
+ "data-style-id": option.styleId,
2750
+ onMouseEnter: () => nav.setSelectedIndex(index),
2751
+ onClick: () => isEnabled && apply(option),
2752
+ children: [
2753
+ /* @__PURE__ */ jsx("span", { style: previewStyle(option), children: styleLabel(option.name) }),
2754
+ changed.has(option.styleId) && /* @__PURE__ */ jsx(
2755
+ "span",
2756
+ {
2757
+ className: "native-docx-style-diverged",
2758
+ title: "Defined differently from the house template",
2759
+ "aria-label": "Defined differently from the house template"
2760
+ }
2761
+ )
2762
+ ]
2763
+ },
2764
+ option.styleId
2765
+ )),
2766
+ shown.length === 0 && /* @__PURE__ */ jsx("li", { className: "native-docx-style-empty", children: "No matching style" })
2910
2767
  ]
2911
2768
  }
2912
- ) })
2769
+ )
2913
2770
  ] });
2914
2771
  }
2915
- function CrossReferenceDialog({
2916
- targets,
2917
- references,
2918
- documentId,
2919
- passages,
2920
- loadPassages,
2921
- labels,
2922
- onCancel,
2923
- onInsertTarget,
2924
- onInsertPassage,
2925
- onClosed
2926
- }) {
2927
- const l = { ...DEFAULT_CROSS_REFERENCES_LABELS, ...labels };
2928
- const [tab, setTab] = useState("this");
2929
- const [query, setQuery] = useState("");
2930
- const [chosen, setChosen] = useState(null);
2931
- const [mode, setMode] = useState("number");
2932
- const shown = useMemo(() => filterTargets(targets, query), [targets, query]);
2933
- const effectiveMode = chosen?.number ? mode : "text";
2934
- const [documents, setDocuments] = useState(null);
2935
- const [docId, setDocId] = useState(null);
2936
- const [passageQuery, setPassageQuery] = useState("");
2937
- const [passage, setPassage] = useState(null);
2938
- const [text, setText] = useState("");
2939
- useEffect(() => {
2940
- if (tab !== "other" || !references || documents) return;
2941
- references.documents().then((list2) => setDocuments(list2.filter((d) => d.id !== documentId))).catch(() => setDocuments([]));
2942
- }, [tab, references, documents, documentId]);
2943
- const chooseDocument = (id) => {
2944
- setDocId(id);
2945
- setPassage(null);
2946
- setText("");
2947
- loadPassages(id);
2948
- };
2949
- const title = documents?.find((d) => d.id === docId)?.title ?? "";
2950
- const list = docId ? passages[docId] : void 0;
2951
- const shownPassages = Array.isArray(list) ? filterPassages(list, passageQuery) : [];
2952
- const canInsert = tab === "this" ? !!chosen : !!(docId && passage && text.trim());
2772
+ function StyleGallery() {
2773
+ const { value, options } = useParagraphStyle();
2774
+ const current = options.find((o) => o.styleId === value);
2775
+ const name = current ? styleLabel(current.name) : "Styles";
2776
+ const house = useHouseTemplate();
2777
+ const diverged = house?.basedOnTemplate ? divergenceSummary(house.divergence) : null;
2778
+ return /* @__PURE__ */ jsx(
2779
+ RibbonMenu,
2780
+ {
2781
+ label: "Styles",
2782
+ title: diverged ? `Styles. ${diverged}` : void 0,
2783
+ className: "native-docx-style-trigger",
2784
+ testId: "docx-style-trigger",
2785
+ icon: /* @__PURE__ */ jsxs(Fragment, { children: [
2786
+ /* @__PURE__ */ jsx("span", { className: "native-docx-style-current", "data-testid": "docx-current-style", children: name }),
2787
+ diverged && /* @__PURE__ */ jsx(
2788
+ "span",
2789
+ {
2790
+ className: "native-docx-style-diverged",
2791
+ "data-testid": "docx-style-diverged",
2792
+ "aria-label": diverged
2793
+ }
2794
+ ),
2795
+ /* @__PURE__ */ jsx(ChevronDown, { size: 12, "aria-hidden": true })
2796
+ ] }),
2797
+ children: (close) => /* @__PURE__ */ jsx(Gallery, { close })
2798
+ }
2799
+ );
2800
+ }
2801
+ var W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
2802
+ var DEFAULT_PARAGRAPH_FONT = {
2803
+ id: "DefaultParagraphFont",
2804
+ type: "character",
2805
+ xml: '<w:style w:type="character" w:default="1" w:styleId="DefaultParagraphFont"><w:name w:val="Default Paragraph Font"/><w:uiPriority w:val="1"/><w:semiHidden/><w:unhideWhenUsed/></w:style>'
2806
+ };
2807
+ function stylesOf(kind) {
2808
+ const Kind = kind === "footnote" ? "Footnote" : "Endnote";
2809
+ const text = `${Kind}Text`;
2810
+ return [
2811
+ {
2812
+ id: text,
2813
+ type: "paragraph",
2814
+ xml: `<w:style w:type="paragraph" w:styleId="${text}"><w:name w:val="${kind} text"/><w:basedOn w:val="Normal"/><w:link w:val="${text}Char"/><w:uiPriority w:val="99"/><w:semiHidden/><w:unhideWhenUsed/><w:pPr><w:spacing w:after="0" w:line="240" w:lineRule="auto"/></w:pPr><w:rPr><w:sz w:val="20"/><w:szCs w:val="20"/></w:rPr></w:style>`
2815
+ },
2816
+ {
2817
+ id: `${text}Char`,
2818
+ type: "character",
2819
+ xml: `<w:style w:type="character" w:customStyle="1" w:styleId="${text}Char"><w:name w:val="${Kind} Text Char"/><w:basedOn w:val="DefaultParagraphFont"/><w:link w:val="${text}"/><w:uiPriority w:val="99"/><w:semiHidden/><w:rPr><w:sz w:val="20"/><w:szCs w:val="20"/></w:rPr></w:style>`
2820
+ },
2821
+ {
2822
+ id: `${Kind}Reference`,
2823
+ type: "character",
2824
+ xml: `<w:style w:type="character" w:styleId="${Kind}Reference"><w:name w:val="${kind} reference"/><w:basedOn w:val="DefaultParagraphFont"/><w:uiPriority w:val="99"/><w:semiHidden/><w:unhideWhenUsed/><w:rPr><w:vertAlign w:val="superscript"/></w:rPr></w:style>`
2825
+ }
2826
+ ];
2827
+ }
2828
+ function noteTextStyle(styles, kind) {
2829
+ const paragraph = styles.filter((style) => style.type === "paragraph");
2830
+ const id = kind === "footnote" ? "FootnoteText" : "EndnoteText";
2831
+ return paragraph.find((style) => style.name.toLowerCase() === `${kind} text`)?.styleId ?? paragraph.find((style) => style.styleId === id)?.styleId;
2832
+ }
2833
+ function missingNoteStyles(styles, kind) {
2834
+ const has = (type, id) => styles.some((style) => style.type === type && style.styleId === id);
2835
+ const text = noteTextStyle(styles, kind);
2836
+ const missing = stylesOf(kind).filter(
2837
+ (style) => style.type === "paragraph" ? !text : !has("character", style.id)
2838
+ );
2839
+ if (!missing.length) return [];
2840
+ const base = has("character", DEFAULT_PARAGRAPH_FONT.id) ? [] : [DEFAULT_PARAGRAPH_FONT];
2841
+ return [...base, ...missing].map((style) => style.xml);
2842
+ }
2843
+ var CONTENT_TYPES = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/><Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/></Types>';
2844
+ function stylesOnlyClipboardHtml(styles) {
2845
+ const fragment = zipSync({
2846
+ "[Content_Types].xml": strToU8(CONTENT_TYPES),
2847
+ "_rels/.rels": strToU8(
2848
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/></Relationships>'
2849
+ ),
2850
+ "word/document.xml": strToU8(
2851
+ `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2852
+ <w:document xmlns:w="${W}"><w:body><w:p/></w:body></w:document>`
2853
+ ),
2854
+ "word/_rels/document.xml.rels": strToU8(
2855
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rIdStyles" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/></Relationships>'
2856
+ ),
2857
+ "word/styles.xml": strToU8(
2858
+ `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2859
+ <w:styles xmlns:w="${W}">${styles.join("")}</w:styles>`
2860
+ )
2861
+ });
2862
+ return fragmentClipboardHtml(fragment, false);
2863
+ }
2864
+ function ensureNoteStyles(editor, kind, direct = (fn) => fn()) {
2865
+ if (!editor.can({ type: "insertNote", noteKind: kind }).ok) return void 0;
2866
+ const missing = missingNoteStyles(editor.getDocumentStyles(), kind);
2867
+ if (missing.length)
2868
+ direct(() => editor.exec({ type: "paste", text: "", html: stylesOnlyClipboardHtml(missing) }));
2869
+ return noteTextStyle(editor.getDocumentStyles(), kind);
2870
+ }
2871
+ function useInsertNote(kind) {
2872
+ const command = useEditorCommand(kind === "footnote" ? "insert.footnote" : "insert.endnote");
2873
+ const editor = useDocxEditor();
2953
2874
  const insert = () => {
2954
- if (tab === "this" && chosen) onInsertTarget(chosen, effectiveMode);
2955
- if (tab === "other" && docId && passage)
2956
- onInsertPassage(
2957
- { documentId: docId, chunkId: passage.chunkId, page: passage.page ?? null },
2958
- text
2959
- );
2875
+ const text = editor && ensureNoteStyles(editor, kind, (fn) => withDirectEditing(editor, fn));
2876
+ if (!command.execute()) return false;
2877
+ if (editor) {
2878
+ if (text)
2879
+ withDirectEditing(editor, () => editor.exec({ type: "setParagraphStyle", styleId: text }));
2880
+ editor.exec({ type: "insertText", text: " " });
2881
+ }
2882
+ return true;
2960
2883
  };
2961
- return /* @__PURE__ */ jsx(Dialog, { open: true, onOpenChange: (open) => !open && onCancel(), children: /* @__PURE__ */ jsxs(
2962
- DialogContent,
2884
+ return { insert, isEnabled: command.isEnabled, disabledReason: command.disabledReason };
2885
+ }
2886
+ function NoteAction({
2887
+ kind,
2888
+ label,
2889
+ shortcut,
2890
+ icon
2891
+ }) {
2892
+ const note = useInsertNote(kind);
2893
+ return /* @__PURE__ */ jsx(RibbonItem, { id: `references-${kind}`, label, children: /* @__PURE__ */ jsx(
2894
+ DocxEditor.Toolbar.Action,
2963
2895
  {
2964
- className: "max-h-[85dvh] overflow-y-auto",
2965
- "data-testid": "docx-cross-reference-dialog",
2966
- onCloseAutoFocus: (event) => {
2967
- if (!onClosed) return;
2968
- event.preventDefault();
2969
- onClosed();
2970
- },
2971
- children: [
2972
- /* @__PURE__ */ jsxs(DialogHeader, { children: [
2973
- /* @__PURE__ */ jsx(DialogTitle, { children: l.title }),
2974
- /* @__PURE__ */ jsx(DialogDescription, { children: l.description })
2975
- ] }),
2976
- /* @__PURE__ */ jsxs(Tabs, { value: tab, onValueChange: (v) => setTab(v), children: [
2977
- /* @__PURE__ */ jsxs(TabsList, { children: [
2978
- /* @__PURE__ */ jsx(TabsTrigger, { value: "this", "data-testid": "docx-cross-reference-this", children: l.thisDocument }),
2979
- references && /* @__PURE__ */ jsx(TabsTrigger, { value: "other", "data-testid": "docx-cross-reference-other", children: l.otherDocument })
2980
- ] }),
2981
- /* @__PURE__ */ jsxs(TabsContent, { value: "this", className: "space-y-3", children: [
2982
- /* @__PURE__ */ jsx(
2983
- Input,
2984
- {
2985
- value: query,
2986
- placeholder: l.filterTargets,
2987
- "aria-label": l.filterTargets,
2988
- onChange: (e) => setQuery(e.target.value),
2989
- "data-testid": "docx-cross-reference-filter"
2990
- }
2991
- ),
2992
- /* @__PURE__ */ jsxs(
2993
- "div",
2994
- {
2995
- role: "listbox",
2996
- "aria-label": l.filterTargets,
2997
- className: "max-h-64 space-y-0.5 overflow-y-auto rounded-md border border-border p-1",
2998
- children: [
2999
- shown.length === 0 && /* @__PURE__ */ jsx("p", { className: "p-2 text-sm text-muted-foreground", children: l.noTargets }),
3000
- shown.map((t) => /* @__PURE__ */ jsxs(
3001
- "button",
3002
- {
3003
- type: "button",
3004
- role: "option",
3005
- "aria-selected": chosen?.paragraphId === t.paragraphId,
3006
- "data-testid": "docx-cross-reference-target",
3007
- onClick: () => setChosen(t),
3008
- onDoubleClick: () => onInsertTarget(t, t.number ? mode : "text"),
3009
- className: cn(
3010
- "flex w-full items-baseline gap-2 rounded px-2 py-1 text-left text-sm",
3011
- chosen?.paragraphId === t.paragraphId ? "bg-accent" : "hover:bg-accent/50"
3012
- ),
3013
- style: { paddingLeft: 8 + t.level * 16 },
3014
- children: [
3015
- /* @__PURE__ */ jsx("span", { className: "shrink-0 font-mono text-xs text-muted-foreground", children: t.marker || l.heading }),
3016
- /* @__PURE__ */ jsx("span", { className: cn("truncate", t.heading && "font-medium"), children: t.text })
3017
- ]
3018
- },
3019
- t.paragraphId
3020
- ))
3021
- ]
3022
- }
3023
- ),
3024
- chosen && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3 text-sm", children: [
3025
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: l.insertAs }),
3026
- chosen.number && /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-1", children: [
3027
- /* @__PURE__ */ jsx(
3028
- "input",
3029
- {
3030
- type: "radio",
3031
- name: "cross-reference-mode",
3032
- checked: effectiveMode === "number",
3033
- onChange: () => setMode("number")
3034
- }
3035
- ),
3036
- l.number
3037
- ] }),
3038
- /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-1", children: [
3039
- /* @__PURE__ */ jsx(
3040
- "input",
3041
- {
3042
- type: "radio",
3043
- name: "cross-reference-mode",
3044
- checked: effectiveMode === "text",
3045
- onChange: () => setMode("text")
3046
- }
3047
- ),
3048
- l.text
3049
- ] }),
3050
- /* @__PURE__ */ jsx(
3051
- "span",
3052
- {
3053
- className: "ml-auto rounded bg-muted px-2 py-0.5",
3054
- "data-testid": "docx-cross-reference-preview",
3055
- children: refResult(chosen, effectiveMode)
3056
- }
3057
- )
3058
- ] })
3059
- ] }),
3060
- references && /* @__PURE__ */ jsxs(TabsContent, { value: "other", className: "space-y-3", children: [
3061
- references.renderDocumentPicker ? references.renderDocumentPicker(chooseDocument, docId) : /* @__PURE__ */ jsxs(
3062
- "select",
3063
- {
3064
- className: "h-9 w-full rounded-md border border-border bg-background px-2 text-sm",
3065
- "aria-label": l.chooseDocument,
3066
- value: docId ?? "",
3067
- onChange: (e) => e.target.value && chooseDocument(e.target.value),
3068
- "data-testid": "docx-cross-reference-document",
3069
- children: [
3070
- /* @__PURE__ */ jsx("option", { value: "", children: l.chooseDocument }),
3071
- (documents ?? []).map((d) => /* @__PURE__ */ jsx("option", { value: d.id, children: d.title }, d.id))
3072
- ]
3073
- }
3074
- ),
3075
- docId && /* @__PURE__ */ jsxs(Fragment, { children: [
3076
- /* @__PURE__ */ jsx(
3077
- Input,
3078
- {
3079
- value: passageQuery,
3080
- placeholder: l.filterPassages,
3081
- "aria-label": l.filterPassages,
3082
- onChange: (e) => setPassageQuery(e.target.value),
3083
- "data-testid": "docx-cross-reference-passage-filter"
3084
- }
3085
- ),
3086
- /* @__PURE__ */ jsx(
3087
- "div",
3088
- {
3089
- className: "max-h-64 space-y-1 overflow-y-auto",
3090
- "data-testid": "docx-cross-reference-passages",
3091
- children: list === "loading" || list === void 0 ? /* @__PURE__ */ jsx("p", { className: "p-2 text-sm text-muted-foreground", children: l.loadingPassages }) : shownPassages.length === 0 ? /* @__PURE__ */ jsx("p", { className: "p-2 text-sm text-muted-foreground", children: l.noPassages }) : shownPassages.map((p, i) => /* @__PURE__ */ jsx(
3092
- FactPassageItem,
3093
- {
3094
- index: i,
3095
- page: p.page,
3096
- excerpt: passageExcerpt(p.text, 200),
3097
- isViewing: passage?.chunkId === p.chunkId,
3098
- onSelect: () => {
3099
- setPassage(p);
3100
- setText(referenceText(title, p));
3101
- },
3102
- testId: "docx-cross-reference-passage"
3103
- },
3104
- p.chunkId
3105
- ))
3106
- }
3107
- ),
3108
- passage && /* @__PURE__ */ jsxs("label", { className: "block space-y-1 text-sm", children: [
3109
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: l.referenceText }),
3110
- /* @__PURE__ */ jsx(
3111
- Input,
3112
- {
3113
- value: text,
3114
- onChange: (e) => setText(e.target.value),
3115
- "data-testid": "docx-cross-reference-text"
3116
- }
3117
- )
3118
- ] })
3119
- ] })
3120
- ] })
3121
- ] }),
3122
- /* @__PURE__ */ jsxs(DialogFooter, { children: [
3123
- /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: onCancel, children: l.cancel }),
3124
- /* @__PURE__ */ jsx(Button, { disabled: !canInsert, onClick: insert, "data-testid": "docx-cross-reference-insert", children: l.insertButton })
3125
- ] })
3126
- ]
2896
+ label: `${label} (${shortcut})`,
2897
+ icon,
2898
+ disabled: !note.isEnabled,
2899
+ disabledReason: note.disabledReason ?? void 0,
2900
+ onSelect: () => void note.insert(),
2901
+ className: `native-docx-insert-${kind}`
3127
2902
  }
3128
2903
  ) });
3129
2904
  }
3130
- function RibbonMenu({
3131
- label,
3132
- icon,
3133
- children,
3134
- className,
3135
- testId,
3136
- title,
3137
- keepMounted = false,
3138
- popoverClassName
3139
- }) {
3140
- const [open, setOpen] = useState(false);
3141
- const element = useRef(null);
3142
- useEffect(() => {
3143
- if (!open) return;
3144
- const dismiss = (event) => {
3145
- if (!element.current?.contains(event.target)) setOpen(false);
3146
- };
3147
- const escape = (event) => {
3148
- if (event.key === "Escape") setOpen(false);
3149
- };
3150
- document.addEventListener("pointerdown", dismiss);
3151
- document.addEventListener("keydown", escape);
3152
- return () => {
3153
- document.removeEventListener("pointerdown", dismiss);
3154
- document.removeEventListener("keydown", escape);
3155
- };
3156
- }, [open]);
3157
- return /* @__PURE__ */ jsxs("div", { className: "native-docx-ribbon-menu", ref: element, children: [
2905
+ function NoteButtons() {
2906
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
3158
2907
  /* @__PURE__ */ jsx(
3159
- "button",
2908
+ NoteAction,
3160
2909
  {
3161
- type: "button",
3162
- className: ["native-docx-ribbon-trigger", className].filter(Boolean).join(" "),
3163
- "aria-label": label,
3164
- title: title ?? label,
3165
- "aria-expanded": open,
3166
- "data-testid": testId,
3167
- onMouseDown: (event) => event.preventDefault(),
3168
- onClick: () => setOpen((value) => !value),
3169
- children: icon
2910
+ kind: "footnote",
2911
+ label: "Insert footnote",
2912
+ shortcut: "Ctrl+Alt+F",
2913
+ icon: /* @__PURE__ */ jsx(Superscript, { size: 16 })
3170
2914
  }
3171
2915
  ),
3172
- (open || keepMounted) && /* @__PURE__ */ jsx(
3173
- "div",
2916
+ /* @__PURE__ */ jsx(
2917
+ NoteAction,
3174
2918
  {
3175
- className: ["native-docx-ribbon-popover", popoverClassName].filter(Boolean).join(" "),
3176
- role: "group",
3177
- "aria-label": label,
3178
- hidden: !open,
3179
- onMouseDown: (event) => {
3180
- if (!(event.target instanceof HTMLInputElement || event.target instanceof HTMLSelectElement))
3181
- event.preventDefault();
3182
- },
3183
- children: typeof children === "function" ? children(() => setOpen(false)) : children
2919
+ kind: "endnote",
2920
+ label: "Insert endnote",
2921
+ shortcut: "Ctrl+Alt+D",
2922
+ icon: /* @__PURE__ */ jsx(NotebookText, { size: 16 })
3184
2923
  }
3185
2924
  )
3186
2925
  ] });
3187
2926
  }
3188
- var titleOf = (prompt) => {
3189
- const line = prompt.trim().split("\n")[0];
3190
- return line.length > 80 ? `${line.slice(0, 79)}\u2026` : line;
3191
- };
3192
- function rememberCaret(editor) {
3193
- const surface = editor.surface;
3194
- const head = surface?.state().selection?.head;
3195
- if (!surface || !head) return null;
3196
- return { paraId: surface.session.paraIdOf(head.paragraphId), ...head };
3197
- }
3198
- var mounted = [];
3199
- var running = 0;
3200
- var listeners = /* @__PURE__ */ new Set();
3201
- var setRunning = (delta) => {
3202
- running += delta;
3203
- listeners.forEach((listener) => listener());
3204
- };
3205
- var subscribe = (listener) => {
3206
- listeners.add(listener);
3207
- return () => void listeners.delete(listener);
3208
- };
3209
- function insertionTarget(origin, caret, editors = mounted) {
3210
- if (editors.includes(origin) && origin.surface) return origin;
3211
- const paraId = caret?.paraId;
3212
- if (!paraId) return null;
3213
- for (let i = editors.length - 1; i >= 0; i--) {
3214
- const session = editors[i].surface?.session;
3215
- if (session?.nodeIdOf(paraId)) return editors[i];
3216
- }
3217
- return null;
3218
- }
3219
- function restoreCaret(editor, caret) {
3220
- const surface = editor.surface;
3221
- if (!surface || !caret) return;
3222
- const paragraphId = caret.paraId && surface.session.nodeIdOf(caret.paraId) || caret.paragraphId;
3223
- if (!surface.session.paragraphIds().includes(paragraphId)) return;
3224
- const point = { paragraphId, offset: caret.offset };
3225
- surface.setSelection({ anchor: point, head: point });
3226
- }
3227
- async function insertGeneratedImage(editor, bytes, prompt, caret) {
3228
- const image = normalizeImageBytes(bytes);
3229
- if (!image.ok) throw new Error("The generated image is not in a format Word can embed.");
3230
- let result;
3231
- for (let attempt = 0; attempt < 3 && !result?.ok; attempt++) {
3232
- editor.focus();
3233
- restoreCaret(editor, caret);
3234
- result = await executeImageCommand(editor, {
3235
- type: "insertImage",
3236
- data: image.bytes,
3237
- mime: image.mime,
3238
- widthPoints: image.widthPoints,
3239
- heightPoints: image.heightPoints,
3240
- description: prompt.trim(),
3241
- title: titleOf(prompt)
3242
- });
3243
- if (!result.ok && result.reason !== "stale drawing selection") break;
3244
- }
3245
- if (!result?.ok) throw new Error(result?.reason || "The picture could not be inserted here.");
3246
- }
3247
- var AUTO = "";
3248
- function useGenerateImageTarget() {
2927
+ function WordShortcuts({ scope }) {
2928
+ const footnote = useInsertNote("footnote");
2929
+ const endnote = useInsertNote("endnote");
2930
+ const styles = useParagraphStyle();
3249
2931
  const editor = useDocxEditor();
3250
- const loaded2 = useEditorState((snapshot) => !snapshot.isLoading);
2932
+ const levels = useHouseTemplate()?.levels;
2933
+ const latest = useRef({ footnote, endnote, styles, editor, levels });
2934
+ latest.current = { footnote, endnote, styles, editor, levels };
3251
2935
  useEffect(() => {
3252
- if (!editor || !loaded2) return;
3253
- mounted.push(editor);
3254
- return () => void mounted.splice(mounted.indexOf(editor), 1);
3255
- }, [editor, loaded2]);
3256
- }
3257
- function GenerateImageButton({ generation }) {
3258
- const editor = useDocxEditor();
3259
- const [prompt, setPrompt] = useState("");
3260
- const [aspectRatio, setAspectRatio] = useState(AUTO);
3261
- const [busy, setBusy] = useState(false);
3262
- const [error, setError] = useState("");
3263
- const abort = useRef(null);
3264
- const close = useRef(() => {
3265
- });
3266
- const editable = useEditorState((snapshot) => snapshot.editable);
3267
- const generating = useSyncExternalStore(subscribe, () => running > 0);
3268
- async function run2() {
3269
- if (!editor || !prompt.trim() || busy) return;
3270
- const caret = rememberCaret(editor);
3271
- const controller = new AbortController();
3272
- abort.current = controller;
3273
- setBusy(true);
3274
- setRunning(1);
3275
- setError("");
3276
- try {
3277
- const bytes = await generation.generate(prompt.trim(), {
3278
- aspectRatio: aspectRatio || void 0,
3279
- signal: controller.signal
3280
- });
3281
- if (controller.signal.aborted) return;
3282
- const target = insertionTarget(editor, caret);
3283
- if (!target) throw new Error("The document was closed before the generated image was ready.");
3284
- await insertGeneratedImage(target, bytes, prompt, caret);
3285
- setPrompt("");
3286
- close.current();
3287
- generation.notify?.("Generated image inserted", "success");
3288
- } catch (failure) {
3289
- if (controller.signal.aborted) return;
3290
- const message = failure instanceof Error ? failure.message : String(failure);
3291
- setError(message);
3292
- generation.notify?.(message, "error");
3293
- } finally {
3294
- abort.current = null;
3295
- setRunning(-1);
3296
- setBusy(false);
3297
- }
3298
- }
3299
- if (!editable && !generating) return null;
3300
- return /* @__PURE__ */ jsx(
3301
- RibbonMenu,
3302
- {
3303
- label: generating ? "Generating image\u2026" : "Generate image",
3304
- icon: generating ? /* @__PURE__ */ jsx(Loader2, { size: 16, className: "native-docx-spin" }) : /* @__PURE__ */ jsx(Sparkles, { size: 16 }),
3305
- testId: "docx-generate-image",
3306
- children: (dismiss) => {
3307
- close.current = dismiss;
3308
- return /* @__PURE__ */ jsxs(
3309
- "form",
3310
- {
3311
- className: "native-docx-form native-docx-generate-image",
3312
- onSubmit: (event) => {
3313
- event.preventDefault();
3314
- void run2();
3315
- },
3316
- children: [
3317
- /* @__PURE__ */ jsxs("label", { children: [
3318
- "Describe the image",
3319
- /* @__PURE__ */ jsx(
3320
- "textarea",
3321
- {
3322
- autoFocus: true,
3323
- rows: 3,
3324
- value: prompt,
3325
- disabled: busy,
3326
- placeholder: "A watercolour of a lighthouse at dawn",
3327
- "data-testid": "docx-generate-image-prompt",
3328
- onChange: (event) => setPrompt(event.target.value),
3329
- onKeyDown: (event) => {
3330
- if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) void run2();
3331
- }
3332
- }
3333
- )
3334
- ] }),
3335
- generation.aspectRatios && generation.aspectRatios.length > 0 && /* @__PURE__ */ jsxs("label", { children: [
3336
- "Aspect ratio",
3337
- /* @__PURE__ */ jsxs(
3338
- "select",
3339
- {
3340
- value: aspectRatio,
3341
- disabled: busy,
3342
- onChange: (event) => setAspectRatio(event.target.value),
3343
- children: [
3344
- /* @__PURE__ */ jsx("option", { value: AUTO, children: "Auto" }),
3345
- generation.aspectRatios.map((ratio) => /* @__PURE__ */ jsx("option", { value: ratio.value, children: ratio.label }, ratio.value))
3346
- ]
3347
- }
3348
- )
3349
- ] }),
3350
- busy && /* @__PURE__ */ jsx("p", { role: "status", "data-testid": "docx-generate-image-status", children: "Generating\u2026 the picture is inserted where your cursor was. You can keep writing." }),
3351
- error && /* @__PURE__ */ jsx("p", { role: "alert", children: error }),
3352
- /* @__PURE__ */ jsx("div", { className: "native-docx-form-actions", children: busy ? /* @__PURE__ */ jsx("button", { type: "button", onClick: () => abort.current?.abort(), children: "Cancel" }) : /* @__PURE__ */ jsx(
3353
- "button",
3354
- {
3355
- type: "submit",
3356
- disabled: !prompt.trim(),
3357
- "data-testid": "docx-generate-image-submit",
3358
- children: "Generate"
3359
- }
3360
- ) })
3361
- ]
3362
- }
3363
- );
2936
+ const element = scope.current;
2937
+ if (!element) return;
2938
+ const onKeyDown = (event) => {
2939
+ if (outlineTab(event)) {
2940
+ event.preventDefault();
2941
+ event.stopPropagation();
2942
+ return;
2943
+ }
2944
+ const action = wordShortcut(event);
2945
+ if (!action) return;
2946
+ const { footnote: footnote2, endnote: endnote2, styles: styles2, editor: editor2 } = latest.current;
2947
+ if (action === "footnote" || action === "endnote") {
2948
+ if (!(action === "footnote" ? footnote2 : endnote2).insert()) return;
2949
+ } else {
2950
+ const style = findStyle(styles2.options, action);
2951
+ if (!style) return;
2952
+ styles2.setValue(style.styleId);
3364
2953
  }
2954
+ event.preventDefault();
2955
+ event.stopPropagation();
2956
+ editor2?.surface?.focus();
2957
+ };
2958
+ const outlineTab = (event) => {
2959
+ if (event.key !== "Tab" || event.ctrlKey || event.metaKey || event.altKey) return false;
2960
+ const { editor: editor2, levels: levels2, styles: styles2 } = latest.current;
2961
+ const current = editor2?.getSelectionFormatting()?.styleId;
2962
+ const link = current ? levels2?.[current] : void 0;
2963
+ const target = event.shiftKey ? link?.previous : link?.next;
2964
+ if (!target || !editor2 || editorPort(editor2).caret()?.offset !== 0) return false;
2965
+ styles2.setValue(target);
2966
+ return true;
2967
+ };
2968
+ element.addEventListener("keydown", onKeyDown, true);
2969
+ return () => element.removeEventListener("keydown", onKeyDown, true);
2970
+ }, [scope]);
2971
+ return null;
2972
+ }
2973
+
2974
+ // src/docx/beforeSave.ts
2975
+ var hooks = /* @__PURE__ */ new WeakMap();
2976
+ function onBeforeSave(editor, fn) {
2977
+ let set = hooks.get(editor);
2978
+ if (!set) hooks.set(editor, set = /* @__PURE__ */ new Set());
2979
+ set.add(fn);
2980
+ return () => set.delete(fn);
2981
+ }
2982
+ function runBeforeSave(editor) {
2983
+ if (!editor) return;
2984
+ for (const fn of hooks.get(editor) ?? [])
2985
+ try {
2986
+ fn();
2987
+ } catch {
2988
+ }
2989
+ }
2990
+
2991
+ // src/docx/references/numbering.ts
2992
+ var numbers = (level) => !!level && level.numFmt !== "bullet" && level.numFmt !== "none" && (level.lvlText ?? level.markerText ?? "").length > 0;
2993
+ function paragraphNumber(order, items, levelOf, paragraphId) {
2994
+ const item = items.get(paragraphId);
2995
+ if (!item || !numbers(item)) return null;
2996
+ const at = order.indexOf(paragraphId);
2997
+ const markerAt = (ilvl) => {
2998
+ if (ilvl === item.ilvl) return item.markerText;
2999
+ for (let i = at - 1; i >= 0; i--) {
3000
+ const other = items.get(order[i]);
3001
+ if (other && other.numId === item.numId && other.ilvl === ilvl) return other.markerText;
3365
3002
  }
3003
+ return null;
3004
+ };
3005
+ const chain = [];
3006
+ const texts = [];
3007
+ for (let u = item.ilvl; u >= 0; u--) {
3008
+ const level = levelOf(item.numId, u);
3009
+ if (!level || !numbers(level)) continue;
3010
+ if (u !== item.ilvl && texts.some((t) => t.includes(`%${u + 1}`))) continue;
3011
+ chain.push(u);
3012
+ texts.push(level.lvlText);
3013
+ }
3014
+ const value = chain.reverse().map((u) => markerAt(u) ?? "").join("");
3015
+ if (!value) return null;
3016
+ return value.length > 1 && value.endsWith(".") ? value.slice(0, -1) : value;
3017
+ }
3018
+ function listTargets(paragraphs, items, levelOf) {
3019
+ const order = paragraphs.map((p) => p.id);
3020
+ const out = [];
3021
+ for (const p of paragraphs) {
3022
+ const text = p.text.replace(//g, "").trim();
3023
+ if (!text) continue;
3024
+ const number = paragraphNumber(order, items, levelOf, p.id);
3025
+ const item = items.get(p.id);
3026
+ if (number !== null)
3027
+ out.push({
3028
+ paragraphId: p.id,
3029
+ kind: "numbered",
3030
+ number,
3031
+ marker: item?.markerText ?? "",
3032
+ text,
3033
+ heading: p.heading,
3034
+ level: item?.ilvl ?? 0
3035
+ });
3036
+ else if (p.heading)
3037
+ out.push({
3038
+ paragraphId: p.id,
3039
+ kind: "heading",
3040
+ number: null,
3041
+ marker: "",
3042
+ text,
3043
+ heading: true,
3044
+ level: 0
3045
+ });
3046
+ }
3047
+ return out;
3048
+ }
3049
+ function filterTargets(targets, query) {
3050
+ const q = query.trim().toLowerCase();
3051
+ if (!q) return [...targets];
3052
+ return targets.filter(
3053
+ (t) => (t.number ?? "").toLowerCase().startsWith(q) || t.text.toLowerCase().includes(q)
3366
3054
  );
3367
3055
  }
3368
- function wordDraftHtml(markdown) {
3369
- const template = document.createElement("template");
3370
- template.innerHTML = renderToStaticMarkup(/* @__PURE__ */ jsx(AiMarkdown, { children: markdown }));
3371
- template.content.querySelectorAll('button, svg, [data-testid="code-block-header"]').forEach((node) => node.remove());
3372
- template.content.querySelectorAll('[data-testid="markdown-code-block"]').forEach((node) => {
3373
- const pre = node.querySelector("pre");
3374
- if (pre) node.replaceWith(pre);
3375
- });
3376
- return template.innerHTML;
3056
+
3057
+ // src/docx/references/commands.ts
3058
+ var REF_BOOKMARK_PREFIX = "_Ref";
3059
+ function newRefBookmark(taken, random = Math.random) {
3060
+ for (; ; ) {
3061
+ const name = `${REF_BOOKMARK_PREFIX}${String(Math.floor(random() * 9e8) + 1e8)}`;
3062
+ if (!taken.has(name)) return name;
3063
+ }
3377
3064
  }
3378
- function requireEdit(result) {
3379
- if (!result.ok) throw new Error(result.reason);
3065
+ var styled = (style) => style ? `<w:rPr><w:rStyle w:val="${style}"/></w:rPr>` : "";
3066
+ var run = (text, style) => `<w:r>${styled(style)}<w:t xml:space="preserve">${esc(text)}</w:t></w:r>`;
3067
+ function refFieldXml(instruction, cached) {
3068
+ const r = (inner) => `<w:r>${styled(CROSS_REFERENCE_STYLE)}${inner}</w:r>`;
3069
+ return `${r('<w:fldChar w:fldCharType="begin"/>')}${r(
3070
+ `<w:instrText xml:space="preserve"> ${esc(instruction)} </w:instrText>`
3071
+ )}${r('<w:fldChar w:fldCharType="separate"/>')}${run(cached, CROSS_REFERENCE_STYLE)}${r(
3072
+ '<w:fldChar w:fldCharType="end"/>'
3073
+ )}`;
3380
3074
  }
3381
- var WordDraftStream = class {
3382
- constructor(editor) {
3383
- this.editor = editor;
3075
+ var refTooltip = (value, mode) => mode === "number" ? `Cross-reference to clause ${value}` : `Cross-reference to \u201C${value}\u201D`;
3076
+ function crossReferenceXml(bookmark, mode, value) {
3077
+ return `<w:hyperlink w:anchor="${esc(bookmark)}" w:tooltip="${esc(
3078
+ refTooltip(value, mode)
3079
+ )}" w:history="1">${refFieldXml(refInstruction(bookmark, mode), value)}</w:hyperlink>`;
3080
+ }
3081
+ var refInstruction = (bookmark, mode) => mode === "number" ? `REF ${bookmark} \\r \\h` : `REF ${bookmark} \\h`;
3082
+ var refResult = (target, mode) => mode === "number" && target.number ? target.number : target.text;
3083
+ var allBookmarks = (port) => new Set(port.paragraphs().flatMap((p) => p.bookmarks));
3084
+ function ensureTargetBookmark(port, target, mode) {
3085
+ const paragraph = port.paragraphs().find((p) => p.id === target.paragraphId);
3086
+ if (!paragraph) throw new Error("That clause is no longer in the document.");
3087
+ const existing = paragraph.bookmarks.find((b) => b.startsWith(REF_BOOKMARK_PREFIX));
3088
+ if (existing && mode === "number") return existing;
3089
+ const name = newRefBookmark(allBookmarks(port));
3090
+ const start = `<w:bookmarkStart w:id="0" w:name="${name}"/>`;
3091
+ const end = '<w:bookmarkEnd w:id="0"/>';
3092
+ if (mode === "number") {
3093
+ port.setCaret({ paragraphId: paragraph.id, offset: 0 });
3094
+ if (!port.paste(`<w:p>${start}${end}</w:p>`, "", false))
3095
+ throw new Error("The cross-reference target could not be marked.");
3096
+ return name;
3384
3097
  }
3385
- editor;
3386
- committed = [];
3387
- pending;
3388
- resetAfterFailure = false;
3389
- update(content, complete) {
3390
- const blocks = splitMarkdownBlocks(content);
3391
- const completeCount = complete ? blocks.length : Math.max(0, blocks.length - 1);
3392
- requireEdit(this.editor.setEditingMode("editing"));
3393
- try {
3394
- if (this.resetAfterFailure || this.committed.some((block, index) => blocks[index] !== block)) {
3395
- requireEdit(this.editor.exec({ type: "selectAll" }));
3396
- requireEdit(this.editor.exec({ type: "deleteText" }));
3397
- this.committed = [];
3398
- this.pending = void 0;
3399
- this.resetAfterFailure = false;
3400
- }
3401
- for (let index = this.committed.length; index < blocks.length; index++) {
3402
- const tail = this.editor.query({ type: "paragraphs" }).at(-1);
3403
- const start = this.pending?.start || tail?.paraId;
3404
- if (!start) throw new Error("The draft insertion paragraph is unavailable.");
3405
- requireEdit(
3406
- this.editor.exec(
3407
- this.pending ? {
3408
- type: "setSelection",
3409
- range: { from: { paraId: start }, to: { paraId: this.pending.end } }
3410
- } : { type: "setSelection", anchor: { paraId: start } }
3411
- )
3412
- );
3413
- const html = wordDraftHtml(blocks[index]) + "<p></p>";
3414
- const element = document.createElement("div");
3415
- element.innerHTML = html;
3416
- const startIndex = this.editor.query({ type: "paragraphs" }).findIndex((p) => p.paraId === start);
3417
- requireEdit(this.editor.exec({ type: "paste", html, text: element.textContent || "" }));
3418
- const inserted = this.editor.query({ type: "paragraphs" }).slice(startIndex);
3419
- let headingIndex = 0;
3420
- for (const heading of Array.from(element.querySelectorAll("h1,h2,h3,h4,h5,h6"))) {
3421
- const match = inserted.findIndex(
3422
- (p, i) => i >= headingIndex && p.text === heading.textContent
3423
- );
3424
- if (match >= 0 && inserted[match].paraId) {
3425
- requireEdit(
3426
- this.editor.exec({
3427
- type: "setSelection",
3428
- anchor: { paraId: inserted[match].paraId }
3429
- })
3430
- );
3431
- requireEdit(
3432
- this.editor.exec({
3433
- type: "setParagraphStyle",
3434
- styleId: `Heading${heading.tagName.slice(1)}`
3435
- })
3436
- );
3437
- headingIndex = match + 1;
3438
- }
3439
- }
3440
- const end = this.editor.query({ type: "paragraphs" }).at(-1)?.paraId;
3441
- if (!end) throw new Error("The draft continuation paragraph is unavailable.");
3442
- if (index < completeCount) {
3443
- this.committed.push(blocks[index]);
3444
- this.pending = void 0;
3445
- } else this.pending = { start, end };
3098
+ const runs = port.runsXml(paragraph.id);
3099
+ if (!runs) throw new Error("This heading cannot be referred to by its text; use its number.");
3100
+ port.deleteRange(
3101
+ { paragraphId: paragraph.id, offset: 0 },
3102
+ { paragraphId: paragraph.id, offset: paragraph.text.length }
3103
+ );
3104
+ if (!port.paste(`<w:p>${start}${runs}${end}</w:p>`, paragraph.text, false))
3105
+ throw new Error("The cross-reference target could not be marked.");
3106
+ return name;
3107
+ }
3108
+ function pastePoint(port, caret) {
3109
+ const paragraph = port.paragraphs().find((p) => p.id === caret.paragraphId);
3110
+ if (paragraph?.text[caret.offset - 1] !== FIELD_ATOM) return caret;
3111
+ port.setCaret(caret);
3112
+ port.typeText(" ");
3113
+ return { paragraphId: caret.paragraphId, offset: caret.offset + 1 };
3114
+ }
3115
+ function markCrossReferenceTarget(port, target, mode) {
3116
+ if (mode === "number" && !target.number) mode = "text";
3117
+ const bookmark = ensureTargetBookmark(port, target, mode);
3118
+ return { bookmark, mode, value: refResult(target, mode) };
3119
+ }
3120
+ function insertCrossReferenceField(port, { bookmark, mode, value }, caret) {
3121
+ caret = pastePoint(port, caret);
3122
+ port.setCaret(caret);
3123
+ if (!port.paste(`<w:p>${crossReferenceXml(bookmark, mode, value)}</w:p>`, value, false))
3124
+ throw new Error("The cross-reference could not be inserted.");
3125
+ port.setCaret({ paragraphId: caret.paragraphId, offset: caret.offset + 1 });
3126
+ return { bookmark };
3127
+ }
3128
+ function insertCrossReference(port, target, mode, caret) {
3129
+ return insertCrossReferenceField(port, markCrossReferenceTarget(port, target, mode), caret);
3130
+ }
3131
+ var documentReferenceTooltip = (text, appName = "ARBI") => `Cross-reference: ${text} \u2014 opens in ${appName}`;
3132
+ function insertDocumentReference(port, reference, caret) {
3133
+ const text = reference.text.trim();
3134
+ if (!text) throw new Error("Enter the reference text.");
3135
+ const tooltip = documentReferenceTooltip(text, reference.appName).slice(0, 255);
3136
+ caret = pastePoint(port, caret);
3137
+ port.setCaret(caret);
3138
+ const link = `<w:p><w:hyperlink r:id="rIdRef1"${tooltip ? ` w:tooltip="${esc(tooltip)}"` : ""} w:history="1">${run(text, CROSS_REFERENCE_STYLE)}</w:hyperlink></w:p>`;
3139
+ if (!port.paste(link, text, false, hyperlinkRelationship("rIdRef1", reference.url)))
3140
+ throw new Error("The reference could not be inserted.");
3141
+ port.setCaret({ paragraphId: caret.paragraphId, offset: caret.offset + text.length });
3142
+ }
3143
+ var REF_FIELD = /^REF\s+(\S+)((?:\s+\\[A-Za-z*]+(?:\s+MERGEFORMAT)?)*)\s*$/i;
3144
+ function staleCrossReferences(port) {
3145
+ const paragraphs = port.paragraphs();
3146
+ if (!paragraphs.some((p) => p.fields?.length)) return [];
3147
+ const items = port.listItems();
3148
+ const order = paragraphs.map((p) => p.id);
3149
+ const owner = /* @__PURE__ */ new Map();
3150
+ for (const p of paragraphs) for (const b of p.bookmarks) if (!owner.has(b)) owner.set(b, p.id);
3151
+ const out = [];
3152
+ for (const p of paragraphs)
3153
+ for (const field of p.fields ?? []) {
3154
+ const match = REF_FIELD.exec(field.instruction);
3155
+ if (!match || !match[1].startsWith(REF_BOOKMARK_PREFIX)) continue;
3156
+ const switches = match[2].toLowerCase();
3157
+ const mode = /\\[rwn]\b/.test(switches) ? "number" : "text";
3158
+ const target = owner.get(match[1]);
3159
+ const base = {
3160
+ paragraphId: p.id,
3161
+ offset: field.offset,
3162
+ instruction: field.instruction,
3163
+ cached: field.result,
3164
+ bookmark: match[1],
3165
+ mode
3166
+ };
3167
+ if (!target) {
3168
+ out.push({ ...base, value: null });
3169
+ continue;
3446
3170
  }
3447
- } catch (error) {
3448
- this.resetAfterFailure = true;
3449
- throw error;
3450
- } finally {
3451
- requireEdit(this.editor.setEditingMode("viewing"));
3171
+ const value = /\\[rwn]\b/.test(switches) ? switches.includes("\\r") ? paragraphNumber(order, items, port.levelOf, target) : null : (paragraphs.find((x) => x.id === target)?.text ?? "").replace(//g, "").trim();
3172
+ if (value && value !== field.result.trim()) out.push({ ...base, value });
3452
3173
  }
3174
+ return out;
3175
+ }
3176
+ function brokenCrossReferences(port) {
3177
+ return port.paragraphs().flatMap(
3178
+ (p) => (p.marks ?? []).filter((m) => m.styleId === BROKEN_CROSS_REFERENCE_STYLE).map((m) => ({ paragraphId: p.id, offset: m.offset, text: m.text }))
3179
+ );
3180
+ }
3181
+ function refreshCrossReferences(port) {
3182
+ const stale = staleCrossReferences(port).sort(
3183
+ (a, b) => a.paragraphId === b.paragraphId ? b.offset - a.offset : 0
3184
+ );
3185
+ let refreshed = 0;
3186
+ for (const ref of stale) {
3187
+ const paragraph = port.paragraphs().find((p) => p.id === ref.paragraphId);
3188
+ if (!paragraph || paragraph.text[ref.offset - 1] === FIELD_ATOM) continue;
3189
+ const at = { paragraphId: ref.paragraphId, offset: ref.offset };
3190
+ port.deleteRange(at, { paragraphId: ref.paragraphId, offset: ref.offset + 1 });
3191
+ port.setCaret(at);
3192
+ const text = ref.value ?? (ref.cached.trim() || ref.bookmark);
3193
+ const body = ref.value === null ? run(text, BROKEN_CROSS_REFERENCE_STYLE) : crossReferenceXml(ref.bookmark, ref.mode, ref.value);
3194
+ if (port.paste(`<w:p>${body}</w:p>`, text, false) && ref.value !== null) refreshed++;
3453
3195
  }
3454
- };
3196
+ return { refreshed, broken: brokenCrossReferences(port) };
3197
+ }
3455
3198
 
3456
- // src/docx/overflowPlan.ts
3457
- function planOverflow(items, available, moreWidth) {
3458
- const collapsed = /* @__PURE__ */ new Set();
3459
- let used = items.reduce((sum, item) => sum + item.width, 0);
3460
- if (used <= available) return collapsed;
3461
- used += moreWidth;
3462
- const order = items.map((item, index) => ({ item, index })).sort((a, b) => a.item.priority - b.item.priority || b.index - a.index);
3463
- for (const { item } of order) {
3464
- if (used <= available) break;
3465
- collapsed.add(item.id);
3466
- used -= item.width;
3467
- }
3468
- return collapsed;
3199
+ // src/docx/references/passages.ts
3200
+ function shortTitle(title) {
3201
+ const base = title.replace(/\.[A-Za-z0-9]{1,5}$/, "").trim();
3202
+ const defined = /\(\s*(?:the\s+)?["“'‘]([^"”'’]{1,30})["”'’]\s*\)/i.exec(base);
3203
+ return defined ? defined[1] : base;
3469
3204
  }
3470
- var Overflow = createContext(null);
3471
- var MORE_WIDTH = 32;
3472
- var sameSet = (a, b) => a.size === b.size && [...a].every((id) => b.has(id));
3473
- function RibbonOverflow({ tab, children }) {
3474
- const row = useRef(null);
3475
- const items = useRef(/* @__PURE__ */ new Map());
3476
- const widths = useRef(/* @__PURE__ */ new Map());
3477
- const [version, setVersion] = useState(0);
3478
- const [collapsed, setCollapsed] = useState(() => /* @__PURE__ */ new Set());
3479
- const [slots, setSlots] = useState({});
3480
- const [menu, setMenu] = useState([]);
3481
- const register = useCallback((id, item) => {
3482
- items.current.set(id, item);
3483
- setVersion((v) => v + 1);
3484
- return () => {
3485
- if (items.current.get(id) === item) items.current.delete(id);
3486
- setVersion((v) => v + 1);
3487
- };
3488
- }, []);
3489
- const plan = useCallback(() => {
3490
- const element = row.current;
3491
- if (!element) return;
3492
- const gap = parseFloat(getComputedStyle(element).columnGap) || 0;
3493
- const ordered = Array.from(element.querySelectorAll("[data-ribbon-item]")).map((node) => node.dataset.ribbonItem).filter((id) => items.current.has(id));
3494
- const measured = ordered.map((id) => {
3495
- const { element: node, priority } = items.current.get(id);
3496
- if (!node.hidden) {
3497
- const width = node.getBoundingClientRect().width;
3498
- widths.current.set(id, width ? width + gap : 0);
3499
- }
3500
- return { id, priority, width: widths.current.get(id) ?? 0 };
3501
- });
3502
- const next = planOverflow(measured, element.clientWidth, MORE_WIDTH);
3503
- setCollapsed((current) => sameSet(current, next) ? current : next);
3504
- const entries = ordered.filter((id) => next.has(id)).map((id) => ({ id, label: items.current.get(id).label }));
3505
- setMenu(
3506
- (current) => current.length === entries.length && current.every((entry, i) => entry.id === entries[i].id && entry.label === entries[i].label) ? current : entries
3507
- );
3508
- }, []);
3509
- useLayoutEffect(plan, [plan, version, tab, collapsed]);
3510
- useLayoutEffect(() => {
3511
- const element = row.current;
3512
- if (!element) return;
3513
- const observer = new ResizeObserver(() => plan());
3514
- observer.observe(element);
3515
- for (const item of items.current.values()) observer.observe(item.element);
3516
- return () => observer.disconnect();
3517
- }, [plan, version]);
3518
- const slotRef = useCallback(
3519
- (id) => (node) => setSlots((current) => {
3520
- if (node ? current[id] === node : !(id in current)) return current;
3521
- const next = { ...current };
3522
- if (node) next[id] = node;
3523
- else delete next[id];
3524
- return next;
3525
- }),
3526
- []
3205
+ function passageClause(text) {
3206
+ const first = text.trim().split("\n")[0] ?? "";
3207
+ const match = /^(?:#+\s*)?(?:\*\*)?(?:clause|section|article)?\s*(\d+(?:\.\d+)*)[.)]?(?:\*\*)?\s+\S/i.exec(
3208
+ first
3527
3209
  );
3528
- const context = useMemo(() => ({ collapsed, slots, register }), [collapsed, slots, register]);
3529
- const shown = menu.filter((entry) => collapsed.has(entry.id));
3530
- return /* @__PURE__ */ jsx(Overflow.Provider, { value: context, children: /* @__PURE__ */ jsxs("div", { className: "native-docx-ribbon-row", ref: row, "data-testid": "docx-ribbon-row", children: [
3531
- children,
3532
- shown.length > 0 && /* @__PURE__ */ jsx(
3533
- RibbonMenu,
3534
- {
3535
- label: "More",
3536
- title: "More commands",
3537
- icon: /* @__PURE__ */ jsx(Ellipsis, { size: 16 }),
3538
- testId: "docx-ribbon-more",
3539
- keepMounted: true,
3540
- popoverClassName: "native-docx-ribbon-more",
3541
- children: (close) => /* @__PURE__ */ jsx(MoreRows, { entries: shown, slotRef, close })
3542
- },
3543
- tab
3544
- )
3545
- ] }) });
3210
+ return match ? match[1] : null;
3546
3211
  }
3547
- function MoreRows({
3548
- entries,
3549
- slotRef,
3550
- close
3551
- }) {
3552
- const refs = useRef(/* @__PURE__ */ new Map());
3553
- const refFor = (id) => {
3554
- let ref = refs.current.get(id);
3555
- if (!ref) refs.current.set(id, ref = slotRef(id));
3556
- return ref;
3557
- };
3558
- const list = useRef(null);
3559
- const latest = useRef(close);
3560
- latest.current = close;
3561
- useEffect(() => {
3562
- const element = list.current;
3563
- if (!element) return;
3564
- const onClick = (event) => {
3565
- const target = event.target;
3566
- const row = target.closest(".native-docx-ribbon-more-row");
3567
- if (!row) return;
3568
- const control = row.querySelector(".native-docx-ribbon-more-control");
3569
- if (!control.contains(target)) {
3570
- control.querySelector("button:not([disabled])")?.click();
3571
- return;
3572
- }
3573
- const button = target.closest("button");
3574
- if (button && !button.disabled && !control.querySelector(".native-docx-ribbon-popover")?.contains(button) && !button.hasAttribute("aria-expanded") && !button.hasAttribute("aria-haspopup"))
3575
- latest.current();
3576
- };
3577
- element.addEventListener("click", onClick, true);
3578
- return () => element.removeEventListener("click", onClick, true);
3579
- }, []);
3580
- return /* @__PURE__ */ jsx("div", { className: "native-docx-ribbon-more-list", ref: list, children: entries.map((entry) => /* @__PURE__ */ jsxs("div", { className: "native-docx-ribbon-more-row", "data-ribbon-more": entry.id, children: [
3581
- /* @__PURE__ */ jsx("span", { className: "native-docx-ribbon-more-control", ref: refFor(entry.id) }),
3582
- /* @__PURE__ */ jsx("span", { className: "native-docx-ribbon-more-label", children: entry.label })
3583
- ] }, entry.id)) });
3212
+ function referenceText(title, passage) {
3213
+ const name = shortTitle(title);
3214
+ const clause = passageClause(passage.text);
3215
+ if (clause) return `${name}, clause ${clause}`;
3216
+ if (passage.page) return `${name}, p. ${passage.page}`;
3217
+ return name;
3584
3218
  }
3585
- function RibbonItem({
3586
- id,
3587
- label,
3588
- priority = 0,
3589
- children
3590
- }) {
3591
- const context = useContext(Overflow);
3592
- const element = useRef(null);
3593
- const register = context?.register;
3594
- useLayoutEffect(() => {
3595
- if (!register || !element.current) return;
3596
- return register(id, { label, priority, element: element.current });
3597
- }, [register, id, label, priority]);
3598
- if (!context) return /* @__PURE__ */ jsx(Fragment, { children });
3599
- const collapsed = context.collapsed.has(id);
3600
- const slot = context.slots[id];
3601
- return /* @__PURE__ */ jsx(
3602
- "span",
3603
- {
3604
- ref: element,
3605
- className: "native-docx-ribbon-item",
3606
- "data-ribbon-item": id,
3607
- hidden: collapsed || void 0,
3608
- children: collapsed ? slot ? createPortal(children, slot) : null : children
3609
- }
3610
- );
3219
+ function passageExcerpt(text, max = 280) {
3220
+ const plain = text.replace(/[#*_`>|]+/g, " ").replace(/\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/\s+/g, " ").trim();
3221
+ return plain.length > max ? `${plain.slice(0, max - 1)}\u2026` : plain;
3611
3222
  }
3612
-
3613
- // src/docx/wordStyles.ts
3614
- var QUICK_STYLES = ["Normal", "Title", "heading 1", "heading 2", "heading 3"];
3615
- function findStyle(options, name) {
3616
- const wanted = name.toLowerCase();
3617
- const id = name.replace(/(^|\s)(\w)/g, (_, _s, c) => c.toUpperCase());
3618
- return options.find((o) => o.name.toLowerCase() === wanted) ?? options.find((o) => o.styleId.toLowerCase() === id.toLowerCase());
3223
+ function filterPassages(passages, query) {
3224
+ const q = query.trim().toLowerCase();
3225
+ return q ? passages.filter((p) => p.text.toLowerCase().includes(q)) : [...passages];
3619
3226
  }
3620
- function styleLabel(name) {
3621
- return name === name.toLowerCase() ? name.replace(/(^|\s)(\w)/g, (m) => m.toUpperCase()) : name;
3227
+ var RUN_PARTS = /* @__PURE__ */ new Set(["rPr", "t", "tab", "br", "lastRenderedPageBreak"]);
3228
+ var SKIPPED = /* @__PURE__ */ new Set(["pPr", "bookmarkStart", "bookmarkEnd", "proofErr", "permStart", "permEnd"]);
3229
+ function serialize(node) {
3230
+ if (node.kind === "textValue") return esc(node.value ?? "");
3231
+ const name = `${node.prefix ?? "w"}:${node.localName}`;
3232
+ const attrs = (node.attributes ?? []).map((a) => ` ${a.prefix ? `${a.prefix}:` : ""}${a.localName}="${esc(a.value)}"`).join("");
3233
+ const children = (node.children ?? []).map((c) => serialize(c)).join("");
3234
+ return children ? `<${name}${attrs}>${children}</${name}>` : `<${name}${attrs}/>`;
3622
3235
  }
3623
- function wordShortcut(event) {
3624
- const mod = event.ctrlKey || event.metaKey;
3625
- if (!mod) return null;
3626
- if (event.altKey && !event.shiftKey) {
3627
- switch (event.code) {
3628
- case "KeyF":
3629
- return "footnote";
3630
- case "KeyD":
3631
- return "endnote";
3632
- case "Digit1":
3633
- case "Numpad1":
3634
- return "heading 1";
3635
- case "Digit2":
3636
- case "Numpad2":
3637
- return "heading 2";
3638
- case "Digit3":
3639
- case "Numpad3":
3640
- return "heading 3";
3641
- }
3236
+ function paragraphRunsXml(paragraph) {
3237
+ const runs = [];
3238
+ for (const child of paragraph.children ?? []) {
3239
+ if (child.localName && SKIPPED.has(child.localName)) continue;
3240
+ if (child.localName !== "r") return null;
3241
+ if ((child.children ?? []).some((c) => !c.localName || !RUN_PARTS.has(c.localName))) return null;
3242
+ runs.push(serialize(child));
3243
+ }
3244
+ return runs.length ? runs.join("") : null;
3245
+ }
3246
+ function findNode2(node, id) {
3247
+ if (node.id === id) return node;
3248
+ for (const child of node.children ?? []) {
3249
+ const found = findNode2(child, id);
3250
+ if (found) return found;
3642
3251
  }
3643
- if (event.shiftKey && !event.altKey && event.code === "KeyN") return "Normal";
3644
- return null;
3645
- }
3646
- function divergenceSummary(divergence) {
3647
- if (!divergence) return null;
3648
- const changed = divergence.styles.filter((s) => s.kind === "changed").map((s) => styleLabel(s.name));
3649
- const missing = divergence.styles.filter((s) => s.kind === "missing").map((s) => styleLabel(s.name));
3650
- const parts = [
3651
- divergence.docDefaults ? "default text formatting" : "",
3652
- changed.length ? `${changed.length === 1 ? "style" : "styles"} ${changed.slice(0, 6).join(", ")}${changed.length > 6 ? "\u2026" : ""}` : "",
3653
- missing.length ? `${missing.length} house ${missing.length === 1 ? "style" : "styles"} missing` : ""
3654
- ].filter(Boolean);
3655
- return parts.length ? `Differs from the house template: ${parts.join("; ")}.` : null;
3252
+ return void 0;
3656
3253
  }
3657
- var FILTER_FROM = 8;
3658
- function previewStyle(option) {
3659
- const { fontFamily, fontSizePt, bold, italic, color } = option.preview;
3254
+ function referencesPort(editor, revision) {
3255
+ const base = editorPort(editor, revision);
3256
+ const session = () => {
3257
+ const s = editor.surface?.session;
3258
+ if (!s) throw new Error("The document is not open.");
3259
+ return s;
3260
+ };
3261
+ const root = () => session().part().root;
3262
+ let numbering;
3263
+ let cascade;
3264
+ const index = () => {
3265
+ if (!numbering) {
3266
+ const s = session();
3267
+ cascade = buildStyleCascadeTable(s.stylesRoot(), void 0, s.settingsRoot());
3268
+ numbering = withNumberingStyleLinks(buildNumberingIndex(s.numberingRoot()), cascade);
3269
+ }
3270
+ return numbering;
3271
+ };
3660
3272
  return {
3661
- fontFamily: fontFamily ? `"${fontFamily}", serif` : void 0,
3662
- // In the face of the style, but within a row: 9-18pt.
3663
- fontSize: fontSizePt ? `${Math.min(18, Math.max(9, fontSizePt))}pt` : void 0,
3664
- fontWeight: bold ? 700 : void 0,
3665
- fontStyle: italic ? "italic" : void 0,
3666
- color: color ? `#${color}` : void 0
3273
+ ...base,
3274
+ listItems: () => {
3275
+ const body = root().children?.find((n) => n.kind === "body");
3276
+ const items = resolveStoryListItems(
3277
+ body?.children ?? [],
3278
+ index(),
3279
+ cascade
3280
+ );
3281
+ const out = /* @__PURE__ */ new Map();
3282
+ for (const [id, item] of items)
3283
+ out.set(id, {
3284
+ numId: item.numId,
3285
+ ilvl: item.ilvl,
3286
+ markerText: item.markerText,
3287
+ numFmt: item.numFmt
3288
+ });
3289
+ return out;
3290
+ },
3291
+ levelOf: (numId, ilvl) => {
3292
+ const level = resolveNumberingLevel(index(), numId, ilvl)?.level;
3293
+ return level ? { lvlText: level.lvlText, numFmt: level.numFmt } : null;
3294
+ },
3295
+ runsXml: (paragraphId) => {
3296
+ const node = findNode2(root(), paragraphId);
3297
+ return node ? paragraphRunsXml(node) : null;
3298
+ }
3667
3299
  };
3668
3300
  }
3669
- function Gallery({ close }) {
3670
- const { value, setValue, options, isEnabled } = useParagraphStyle();
3301
+ var DEFAULT_CROSS_REFERENCES_LABELS = {
3302
+ insert: "Insert cross-reference",
3303
+ title: "Insert cross-reference",
3304
+ description: "Refer to a clause of this document, or to a passage of another document.",
3305
+ thisDocument: "This document",
3306
+ otherDocument: "Another document",
3307
+ filterTargets: "Find a clause or heading",
3308
+ noTargets: "This document has no numbered clauses or headings yet.",
3309
+ number: "Paragraph number",
3310
+ text: "Heading text",
3311
+ insertAs: "Insert",
3312
+ chooseDocument: "Choose a document",
3313
+ filterPassages: "Find a passage",
3314
+ loadingPassages: "Loading passages\u2026",
3315
+ noPassages: "No passage matches.",
3316
+ referenceText: "Reference text",
3317
+ insertButton: "Insert",
3318
+ cancel: "Cancel",
3319
+ clause: (number) => `Clause ${number}`,
3320
+ heading: "Heading",
3321
+ update: "Update cross-references",
3322
+ updated: "Cross-references updated",
3323
+ updateSummary: (refreshed) => refreshed ? `${refreshed} cross-reference${refreshed === 1 ? "" : "s"} brought up to date.` : "Every cross-reference is up to date.",
3324
+ brokenTitle: "Broken cross-references",
3325
+ brokenNone: "No cross-reference points at a deleted target.",
3326
+ brokenHelp: "Their target was deleted: the last text is kept and marked. Insert the reference again to repair it.",
3327
+ brokenCard: "Broken cross-reference",
3328
+ brokenCardBody: "Its target was deleted. Update cross-references to mark it.",
3329
+ show: "Show",
3330
+ close: "Close"
3331
+ };
3332
+ var CrossReferencesContext = createContext(null);
3333
+ var useCrossReferences = () => useContext(CrossReferencesContext);
3334
+ function CrossReferencesProvider({
3335
+ children,
3336
+ editable,
3337
+ references,
3338
+ documentId,
3339
+ notify,
3340
+ labels
3341
+ }) {
3671
3342
  const editor = useDocxEditor();
3672
- const house = useHouseTemplate();
3673
- const changed = new Set(
3674
- house?.basedOnTemplate ? house.divergence?.styles.filter((s) => s.kind === "changed").map((s) => s.styleId) : []
3343
+ const track = useTrackedOrNoted();
3344
+ const l = useMemo(() => ({ ...DEFAULT_CROSS_REFERENCES_LABELS, ...labels }), [labels]);
3345
+ const [dialog, setDialog] = useState(
3346
+ null
3675
3347
  );
3676
- const [query, setQuery] = useState("");
3677
- const quick = useMemo(
3678
- () => QUICK_STYLES.map((name) => findStyle(options, name)).filter(
3679
- (o) => !!o
3680
- ),
3681
- [options]
3348
+ const marker = useRef(null);
3349
+ const [viewer, setViewer] = useState(null);
3350
+ useEffect(() => setViewer(marker.current?.closest(".native-docx-viewer") ?? null), []);
3351
+ const openDialog = useCallback(() => {
3352
+ if (!editor || !editable) return;
3353
+ try {
3354
+ const port = referencesPort(editor);
3355
+ const caret = port.caret();
3356
+ if (!caret) return notify?.("Place the cursor where the reference should go.", "error");
3357
+ setDialog({ caret, targets: listTargets(port.paragraphs(), port.listItems(), port.levelOf) });
3358
+ } catch (error) {
3359
+ notify?.(error instanceof Error ? error.message : "Could not read the document.", "error");
3360
+ }
3361
+ }, [editor, editable, notify]);
3362
+ const run2 = useCallback(
3363
+ (label, prepare, insert) => {
3364
+ if (!editor) return;
3365
+ try {
3366
+ const prepared = withDirectEditing(editor, prepare);
3367
+ if (dialog) referencesPort(editor).setCaret(dialog.caret);
3368
+ track({ label, insert: (revision) => insert(prepared, revision), inserted: () => true });
3369
+ setDialog(null);
3370
+ editor.surface?.focus();
3371
+ holdCaret(editor);
3372
+ } catch (error) {
3373
+ notify?.(
3374
+ error instanceof Error ? error.message : "Could not insert the reference.",
3375
+ "error"
3376
+ );
3377
+ }
3378
+ },
3379
+ [editor, dialog, track, notify]
3682
3380
  );
3683
- const shown = useMemo(() => {
3684
- const q = query.trim().toLowerCase();
3685
- return q ? options.filter(
3686
- (o) => o.name.toLowerCase().includes(q) || o.styleId.toLowerCase().includes(q)
3687
- ) : options;
3688
- }, [options, query]);
3689
- const apply = (option) => {
3690
- setValue(option.styleId);
3691
- close();
3692
- editor?.surface?.focus();
3381
+ useEffect(() => {
3382
+ if (!editor || !editable) return;
3383
+ const refresh = () => {
3384
+ try {
3385
+ const port = referencesPort(editor);
3386
+ if (!port.paragraphs().some((p) => p.fields?.length)) return;
3387
+ preservingCaret(editor, () => withDirectEditing(editor, () => refreshCrossReferences(port)));
3388
+ } catch {
3389
+ }
3390
+ };
3391
+ const off = onBeforeSave(editor, refresh);
3392
+ const timer = setTimeout(refresh, 1500);
3393
+ return () => {
3394
+ off();
3395
+ clearTimeout(timer);
3396
+ };
3397
+ }, [editor, editable]);
3398
+ const [report, setReport] = useState(null);
3399
+ const update = useCallback(() => {
3400
+ if (!editor || !editable) return;
3401
+ try {
3402
+ const result = preservingCaret(
3403
+ editor,
3404
+ () => withDirectEditing(editor, () => refreshCrossReferences(referencesPort(editor)))
3405
+ );
3406
+ setReport(result);
3407
+ } catch (error) {
3408
+ notify?.(
3409
+ error instanceof Error ? error.message : "Could not update cross-references.",
3410
+ "error"
3411
+ );
3412
+ }
3413
+ }, [editor, editable, notify]);
3414
+ const [passages, setPassages] = useState({});
3415
+ const loadPassages = useCallback(
3416
+ (id) => {
3417
+ if (!references || passages[id]) return;
3418
+ setPassages((p) => ({ ...p, [id]: "loading" }));
3419
+ references.passages(id).then((list) => setPassages((p) => ({ ...p, [id]: list }))).catch(() => setPassages((p) => ({ ...p, [id]: [] })));
3420
+ },
3421
+ [references, passages]
3422
+ );
3423
+ useEffect(() => {
3424
+ if (!viewer || !references) return;
3425
+ const targetOf = (event) => {
3426
+ const link = event.target?.closest?.(
3427
+ 'a.docx-hyperlink[data-docx-link-kind="external"]'
3428
+ );
3429
+ if (!link || !viewer.contains(link)) return null;
3430
+ const mouse = event;
3431
+ if (mouse.button !== 0 || mouse.ctrlKey || mouse.metaKey || mouse.shiftKey) return null;
3432
+ return references.targetOf(link.getAttribute("href") ?? "");
3433
+ };
3434
+ const take = (event) => {
3435
+ const target = targetOf(event);
3436
+ if (!target) return;
3437
+ event.preventDefault();
3438
+ event.stopPropagation();
3439
+ if (event.type === "click") references.open(target);
3440
+ };
3441
+ const types = ["pointerdown", "mousedown", "pointerup", "mouseup", "click"];
3442
+ for (const type of types) window.addEventListener(type, take, true);
3443
+ return () => {
3444
+ for (const type of types) window.removeEventListener(type, take, true);
3445
+ };
3446
+ }, [viewer, references]);
3447
+ const value = useMemo(
3448
+ () => editable ? { openDialog, update, labels: l } : null,
3449
+ [editable, openDialog, update, l]
3450
+ );
3451
+ const resolveInternal = (href) => {
3452
+ if (!editor) return null;
3453
+ const name = href.replace(/^#/, "");
3454
+ try {
3455
+ const port = referencesPort(editor);
3456
+ const paragraphs = port.paragraphs();
3457
+ const target = paragraphs.find((p) => p.bookmarks.includes(name));
3458
+ if (!target) return { title: l.brokenCard, body: l.brokenCardBody };
3459
+ const number = paragraphNumber(
3460
+ paragraphs.map((p) => p.id),
3461
+ port.listItems(),
3462
+ port.levelOf,
3463
+ target.id
3464
+ );
3465
+ return {
3466
+ title: number ? l.clause(number) : l.heading,
3467
+ body: passageExcerpt(target.text.replace(//g, ""))
3468
+ };
3469
+ } catch {
3470
+ return null;
3471
+ }
3693
3472
  };
3694
- const nav = useListKeyNav({
3695
- open: true,
3696
- items: shown,
3697
- onPick: apply,
3698
- onDismiss: close,
3699
- resetKey: query
3700
- });
3701
- return /* @__PURE__ */ jsxs("div", { className: "native-docx-style-gallery", "data-testid": "docx-style-gallery", children: [
3702
- quick.length > 0 && /* @__PURE__ */ jsx("div", { className: "native-docx-style-quick", role: "group", "aria-label": "Quick styles", children: quick.map((option) => /* @__PURE__ */ jsx(
3703
- "button",
3704
- {
3705
- type: "button",
3706
- "aria-pressed": value === option.styleId,
3707
- disabled: !isEnabled,
3708
- "data-style-id": option.styleId,
3709
- title: styleLabel(option.name),
3710
- onClick: () => apply(option),
3711
- children: /* @__PURE__ */ jsx("span", { style: previewStyle(option), children: styleLabel(option.name) })
3712
- },
3713
- option.styleId
3714
- )) }),
3715
- options.length > FILTER_FROM && /* @__PURE__ */ jsx(
3716
- "input",
3473
+ return /* @__PURE__ */ jsxs(CrossReferencesContext.Provider, { value, children: [
3474
+ /* @__PURE__ */ jsx("span", { ref: marker, hidden: true }),
3475
+ children,
3476
+ /* @__PURE__ */ jsx(
3477
+ LinkHoverCard,
3717
3478
  {
3718
- className: "native-docx-style-filter",
3719
- placeholder: "Find a style",
3720
- "aria-label": "Find a style",
3721
- value: query,
3722
- autoFocus: true,
3723
- onChange: (event) => setQuery(event.target.value),
3724
- onKeyDown: (event) => {
3725
- nav.handleKeyDown(event);
3726
- },
3727
- ...nav.comboboxProps
3479
+ root: viewer,
3480
+ matches: (href) => href.startsWith(`#${REF_BOOKMARK_PREFIX}`),
3481
+ resolve: resolveInternal,
3482
+ testId: "docx-cross-reference-card"
3728
3483
  }
3729
3484
  ),
3730
- /* @__PURE__ */ jsxs(
3731
- "ul",
3485
+ references && /* @__PURE__ */ jsx(
3486
+ LinkHoverCard,
3732
3487
  {
3733
- className: "native-docx-style-list",
3734
- role: "listbox",
3735
- id: nav.listboxId,
3736
- "aria-label": "Document styles",
3737
- children: [
3738
- shown.map((option, index) => /* @__PURE__ */ jsxs(
3739
- "li",
3740
- {
3741
- id: listOptionId(nav.listboxId, index),
3742
- role: "option",
3743
- "aria-selected": value === option.styleId,
3744
- "aria-disabled": !isEnabled,
3745
- "data-active": index === nav.selectedIndex || void 0,
3746
- "data-style-id": option.styleId,
3747
- onMouseEnter: () => nav.setSelectedIndex(index),
3748
- onClick: () => isEnabled && apply(option),
3749
- children: [
3750
- /* @__PURE__ */ jsx("span", { style: previewStyle(option), children: styleLabel(option.name) }),
3751
- changed.has(option.styleId) && /* @__PURE__ */ jsx(
3752
- "span",
3753
- {
3754
- className: "native-docx-style-diverged",
3755
- title: "Defined differently from the house template",
3756
- "aria-label": "Defined differently from the house template"
3757
- }
3758
- )
3759
- ]
3760
- },
3761
- option.styleId
3762
- )),
3763
- shown.length === 0 && /* @__PURE__ */ jsx("li", { className: "native-docx-style-empty", children: "No matching style" })
3764
- ]
3488
+ root: viewer,
3489
+ matches: (href) => !!references.targetOf(href),
3490
+ resolve: (href) => {
3491
+ const target = references.targetOf(href);
3492
+ if (!target) return null;
3493
+ const list = passages[target.documentId];
3494
+ if (!list) loadPassages(target.documentId);
3495
+ const passage = Array.isArray(list) ? list.find((p) => p.chunkId === target.chunkId) : void 0;
3496
+ return passage ? {
3497
+ body: passageExcerpt(passage.text),
3498
+ footer: passage.page ? `p. ${passage.page}` : void 0
3499
+ } : null;
3500
+ },
3501
+ testId: "docx-cross-reference-card"
3765
3502
  }
3766
- )
3767
- ] });
3768
- }
3769
- function StyleGallery() {
3770
- const { value, options } = useParagraphStyle();
3771
- const current = options.find((o) => o.styleId === value);
3772
- const name = current ? styleLabel(current.name) : "Styles";
3773
- const house = useHouseTemplate();
3774
- const diverged = house?.basedOnTemplate ? divergenceSummary(house.divergence) : null;
3775
- return /* @__PURE__ */ jsx(
3776
- RibbonMenu,
3777
- {
3778
- label: "Styles",
3779
- title: diverged ? `Styles. ${diverged}` : void 0,
3780
- className: "native-docx-style-trigger",
3781
- testId: "docx-style-trigger",
3782
- icon: /* @__PURE__ */ jsxs(Fragment, { children: [
3783
- /* @__PURE__ */ jsx("span", { className: "native-docx-style-current", "data-testid": "docx-current-style", children: name }),
3784
- diverged && /* @__PURE__ */ jsx(
3785
- "span",
3786
- {
3787
- className: "native-docx-style-diverged",
3788
- "data-testid": "docx-style-diverged",
3789
- "aria-label": diverged
3790
- }
3791
- ),
3792
- /* @__PURE__ */ jsx(ChevronDown, { size: 12, "aria-hidden": true })
3793
- ] }),
3794
- children: (close) => /* @__PURE__ */ jsx(Gallery, { close })
3795
- }
3796
- );
3797
- }
3798
- var W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
3799
- var DEFAULT_PARAGRAPH_FONT = {
3800
- id: "DefaultParagraphFont",
3801
- type: "character",
3802
- xml: '<w:style w:type="character" w:default="1" w:styleId="DefaultParagraphFont"><w:name w:val="Default Paragraph Font"/><w:uiPriority w:val="1"/><w:semiHidden/><w:unhideWhenUsed/></w:style>'
3803
- };
3804
- function stylesOf(kind) {
3805
- const Kind = kind === "footnote" ? "Footnote" : "Endnote";
3806
- const text = `${Kind}Text`;
3807
- return [
3808
- {
3809
- id: text,
3810
- type: "paragraph",
3811
- xml: `<w:style w:type="paragraph" w:styleId="${text}"><w:name w:val="${kind} text"/><w:basedOn w:val="Normal"/><w:link w:val="${text}Char"/><w:uiPriority w:val="99"/><w:semiHidden/><w:unhideWhenUsed/><w:pPr><w:spacing w:after="0" w:line="240" w:lineRule="auto"/></w:pPr><w:rPr><w:sz w:val="20"/><w:szCs w:val="20"/></w:rPr></w:style>`
3812
- },
3813
- {
3814
- id: `${text}Char`,
3815
- type: "character",
3816
- xml: `<w:style w:type="character" w:customStyle="1" w:styleId="${text}Char"><w:name w:val="${Kind} Text Char"/><w:basedOn w:val="DefaultParagraphFont"/><w:link w:val="${text}"/><w:uiPriority w:val="99"/><w:semiHidden/><w:rPr><w:sz w:val="20"/><w:szCs w:val="20"/></w:rPr></w:style>`
3817
- },
3818
- {
3819
- id: `${Kind}Reference`,
3820
- type: "character",
3821
- xml: `<w:style w:type="character" w:styleId="${Kind}Reference"><w:name w:val="${kind} reference"/><w:basedOn w:val="DefaultParagraphFont"/><w:uiPriority w:val="99"/><w:semiHidden/><w:unhideWhenUsed/><w:rPr><w:vertAlign w:val="superscript"/></w:rPr></w:style>`
3822
- }
3823
- ];
3824
- }
3825
- function noteTextStyle(styles, kind) {
3826
- const paragraph = styles.filter((style) => style.type === "paragraph");
3827
- const id = kind === "footnote" ? "FootnoteText" : "EndnoteText";
3828
- return paragraph.find((style) => style.name.toLowerCase() === `${kind} text`)?.styleId ?? paragraph.find((style) => style.styleId === id)?.styleId;
3829
- }
3830
- function missingNoteStyles(styles, kind) {
3831
- const has = (type, id) => styles.some((style) => style.type === type && style.styleId === id);
3832
- const text = noteTextStyle(styles, kind);
3833
- const missing = stylesOf(kind).filter(
3834
- (style) => style.type === "paragraph" ? !text : !has("character", style.id)
3835
- );
3836
- if (!missing.length) return [];
3837
- const base = has("character", DEFAULT_PARAGRAPH_FONT.id) ? [] : [DEFAULT_PARAGRAPH_FONT];
3838
- return [...base, ...missing].map((style) => style.xml);
3839
- }
3840
- var CONTENT_TYPES = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/><Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/></Types>';
3841
- function stylesOnlyClipboardHtml(styles) {
3842
- const fragment = zipSync({
3843
- "[Content_Types].xml": strToU8(CONTENT_TYPES),
3844
- "_rels/.rels": strToU8(
3845
- '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/></Relationships>'
3846
- ),
3847
- "word/document.xml": strToU8(
3848
- `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3849
- <w:document xmlns:w="${W}"><w:body><w:p/></w:body></w:document>`
3850
3503
  ),
3851
- "word/_rels/document.xml.rels": strToU8(
3852
- '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rIdStyles" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/></Relationships>'
3853
- ),
3854
- "word/styles.xml": strToU8(
3855
- `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
3856
- <w:styles xmlns:w="${W}">${styles.join("")}</w:styles>`
3857
- )
3858
- });
3859
- return fragmentClipboardHtml(fragment, false);
3860
- }
3861
- function ensureNoteStyles(editor, kind, direct = (fn) => fn()) {
3862
- if (!editor.can({ type: "insertNote", noteKind: kind }).ok) return void 0;
3863
- const missing = missingNoteStyles(editor.getDocumentStyles(), kind);
3864
- if (missing.length)
3865
- direct(() => editor.exec({ type: "paste", text: "", html: stylesOnlyClipboardHtml(missing) }));
3866
- return noteTextStyle(editor.getDocumentStyles(), kind);
3867
- }
3868
- function useInsertNote(kind) {
3869
- const command = useEditorCommand(kind === "footnote" ? "insert.footnote" : "insert.endnote");
3870
- const editor = useDocxEditor();
3871
- const insert = () => {
3872
- const text = editor && ensureNoteStyles(editor, kind, (fn) => withDirectEditing(editor, fn));
3873
- if (!command.execute()) return false;
3874
- if (editor) {
3875
- if (text)
3876
- withDirectEditing(editor, () => editor.exec({ type: "setParagraphStyle", styleId: text }));
3877
- editor.exec({ type: "insertText", text: " " });
3878
- }
3879
- return true;
3880
- };
3881
- return { insert, isEnabled: command.isEnabled, disabledReason: command.disabledReason };
3882
- }
3883
- function NoteAction({
3884
- kind,
3885
- label,
3886
- shortcut,
3887
- icon
3888
- }) {
3889
- const note = useInsertNote(kind);
3890
- return /* @__PURE__ */ jsx(RibbonItem, { id: `references-${kind}`, label, children: /* @__PURE__ */ jsx(
3891
- DocxEditor.Toolbar.Action,
3892
- {
3893
- label: `${label} (${shortcut})`,
3894
- icon,
3895
- disabled: !note.isEnabled,
3896
- disabledReason: note.disabledReason ?? void 0,
3897
- onSelect: () => void note.insert(),
3898
- className: `native-docx-insert-${kind}`
3899
- }
3900
- ) });
3901
- }
3902
- function NoteButtons() {
3903
- return /* @__PURE__ */ jsxs(Fragment, { children: [
3904
- /* @__PURE__ */ jsx(
3905
- NoteAction,
3504
+ dialog && /* @__PURE__ */ jsx(
3505
+ CrossReferenceDialog,
3906
3506
  {
3907
- kind: "footnote",
3908
- label: "Insert footnote",
3909
- shortcut: "Ctrl+Alt+F",
3910
- icon: /* @__PURE__ */ jsx(Superscript, { size: 16 })
3507
+ targets: dialog.targets,
3508
+ references,
3509
+ documentId,
3510
+ passages,
3511
+ loadPassages,
3512
+ labels: l,
3513
+ onCancel: () => setDialog(null),
3514
+ onClosed: () => editor?.surface?.focus(),
3515
+ onInsertTarget: (target, mode) => run2(
3516
+ `Inserted cross-reference: ${refResult(target, mode)}`,
3517
+ () => markCrossReferenceTarget(referencesPort(editor), target, mode),
3518
+ (marked, revision) => insertCrossReferenceField(referencesPort(editor, revision), marked, dialog.caret)
3519
+ ),
3520
+ onInsertPassage: (target, text) => run2(
3521
+ `Inserted cross-reference: ${text}`,
3522
+ () => void 0,
3523
+ (_, revision) => insertDocumentReference(
3524
+ referencesPort(editor, revision),
3525
+ { text, url: references.linkFor(target), appName: references.appName },
3526
+ dialog.caret
3527
+ )
3528
+ )
3911
3529
  }
3912
3530
  ),
3913
- /* @__PURE__ */ jsx(
3914
- NoteAction,
3531
+ /* @__PURE__ */ jsx(Dialog, { open: !!report, onOpenChange: (open) => !open && setReport(null), children: /* @__PURE__ */ jsxs(
3532
+ DialogContent,
3915
3533
  {
3916
- kind: "endnote",
3917
- label: "Insert endnote",
3918
- shortcut: "Ctrl+Alt+D",
3919
- icon: /* @__PURE__ */ jsx(NotebookText, { size: 16 })
3534
+ className: "max-h-[85dvh] overflow-y-auto",
3535
+ "data-testid": "docx-cross-references-report",
3536
+ children: [
3537
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
3538
+ /* @__PURE__ */ jsx(DialogTitle, { children: l.updated }),
3539
+ /* @__PURE__ */ jsx(DialogDescription, { children: report && l.updateSummary(report.refreshed) })
3540
+ ] }),
3541
+ report && /* @__PURE__ */ jsxs("section", { className: "space-y-1 text-sm", children: [
3542
+ /* @__PURE__ */ jsx("h3", { className: "font-medium", children: l.brokenTitle }),
3543
+ report.broken.length ? /* @__PURE__ */ jsxs(Fragment, { children: [
3544
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: l.brokenHelp }),
3545
+ /* @__PURE__ */ jsx("ul", { className: "space-y-1", "data-testid": "docx-cross-references-broken", children: report.broken.map((b) => /* @__PURE__ */ jsxs(
3546
+ "li",
3547
+ {
3548
+ className: "flex items-center justify-between gap-2",
3549
+ children: [
3550
+ /* @__PURE__ */ jsx("span", { className: "text-red-text", children: b.text }),
3551
+ /* @__PURE__ */ jsx(
3552
+ Button,
3553
+ {
3554
+ size: "sm",
3555
+ variant: "ghost",
3556
+ onClick: () => {
3557
+ const point = { paragraphId: b.paragraphId, offset: b.offset };
3558
+ editor?.surface?.setSelection({ anchor: point, head: point });
3559
+ editor?.surface?.revealParagraph(b.paragraphId);
3560
+ setReport(null);
3561
+ },
3562
+ children: l.show
3563
+ }
3564
+ )
3565
+ ]
3566
+ },
3567
+ `${b.paragraphId}:${b.offset}`
3568
+ )) })
3569
+ ] }) : /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: l.brokenNone })
3570
+ ] }),
3571
+ /* @__PURE__ */ jsx(DialogFooter, { children: /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => setReport(null), children: l.close }) })
3572
+ ]
3920
3573
  }
3921
- )
3574
+ ) })
3922
3575
  ] });
3923
3576
  }
3924
- function WordShortcuts({ scope }) {
3925
- const footnote = useInsertNote("footnote");
3926
- const endnote = useInsertNote("endnote");
3927
- const styles = useParagraphStyle();
3928
- const editor = useDocxEditor();
3929
- const levels = useHouseTemplate()?.levels;
3930
- const latest = useRef({ footnote, endnote, styles, editor, levels });
3931
- latest.current = { footnote, endnote, styles, editor, levels };
3577
+ function CrossReferenceDialog({
3578
+ targets,
3579
+ references,
3580
+ documentId,
3581
+ passages,
3582
+ loadPassages,
3583
+ labels,
3584
+ onCancel,
3585
+ onInsertTarget,
3586
+ onInsertPassage,
3587
+ onClosed
3588
+ }) {
3589
+ const l = { ...DEFAULT_CROSS_REFERENCES_LABELS, ...labels };
3590
+ const [tab, setTab] = useState("this");
3591
+ const [query, setQuery] = useState("");
3592
+ const [chosen, setChosen] = useState(null);
3593
+ const [mode, setMode] = useState("number");
3594
+ const shown = useMemo(() => filterTargets(targets, query), [targets, query]);
3595
+ const effectiveMode = chosen?.number ? mode : "text";
3596
+ const [documents, setDocuments] = useState(null);
3597
+ const [docId, setDocId] = useState(null);
3598
+ const [passageQuery, setPassageQuery] = useState("");
3599
+ const [passage, setPassage] = useState(null);
3600
+ const [text, setText] = useState("");
3932
3601
  useEffect(() => {
3933
- const element = scope.current;
3934
- if (!element) return;
3935
- const onKeyDown = (event) => {
3936
- if (outlineTab(event)) {
3602
+ if (tab !== "other" || !references || documents) return;
3603
+ references.documents().then((list2) => setDocuments(list2.filter((d) => d.id !== documentId))).catch(() => setDocuments([]));
3604
+ }, [tab, references, documents, documentId]);
3605
+ const chooseDocument = (id) => {
3606
+ setDocId(id);
3607
+ setPassage(null);
3608
+ setText("");
3609
+ loadPassages(id);
3610
+ };
3611
+ const title = documents?.find((d) => d.id === docId)?.title ?? "";
3612
+ const list = docId ? passages[docId] : void 0;
3613
+ const shownPassages = Array.isArray(list) ? filterPassages(list, passageQuery) : [];
3614
+ const canInsert = tab === "this" ? !!chosen : !!(docId && passage && text.trim());
3615
+ const insert = () => {
3616
+ if (tab === "this" && chosen) onInsertTarget(chosen, effectiveMode);
3617
+ if (tab === "other" && docId && passage)
3618
+ onInsertPassage(
3619
+ { documentId: docId, chunkId: passage.chunkId, page: passage.page ?? null },
3620
+ text
3621
+ );
3622
+ };
3623
+ return /* @__PURE__ */ jsx(Dialog, { open: true, onOpenChange: (open) => !open && onCancel(), children: /* @__PURE__ */ jsxs(
3624
+ DialogContent,
3625
+ {
3626
+ className: "max-h-[85dvh] overflow-y-auto",
3627
+ "data-testid": "docx-cross-reference-dialog",
3628
+ onCloseAutoFocus: (event) => {
3629
+ if (!onClosed) return;
3937
3630
  event.preventDefault();
3938
- event.stopPropagation();
3939
- return;
3940
- }
3941
- const action = wordShortcut(event);
3942
- if (!action) return;
3943
- const { footnote: footnote2, endnote: endnote2, styles: styles2, editor: editor2 } = latest.current;
3944
- if (action === "footnote" || action === "endnote") {
3945
- if (!(action === "footnote" ? footnote2 : endnote2).insert()) return;
3946
- } else {
3947
- const style = findStyle(styles2.options, action);
3948
- if (!style) return;
3949
- styles2.setValue(style.styleId);
3950
- }
3951
- event.preventDefault();
3952
- event.stopPropagation();
3953
- editor2?.surface?.focus();
3954
- };
3955
- const outlineTab = (event) => {
3956
- if (event.key !== "Tab" || event.ctrlKey || event.metaKey || event.altKey) return false;
3957
- const { editor: editor2, levels: levels2, styles: styles2 } = latest.current;
3958
- const current = editor2?.getSelectionFormatting()?.styleId;
3959
- const link = current ? levels2?.[current] : void 0;
3960
- const target = event.shiftKey ? link?.previous : link?.next;
3961
- if (!target || !editor2 || editorPort(editor2).caret()?.offset !== 0) return false;
3962
- styles2.setValue(target);
3963
- return true;
3964
- };
3965
- element.addEventListener("keydown", onKeyDown, true);
3966
- return () => element.removeEventListener("keydown", onKeyDown, true);
3967
- }, [scope]);
3968
- return null;
3631
+ onClosed();
3632
+ },
3633
+ children: [
3634
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
3635
+ /* @__PURE__ */ jsx(DialogTitle, { children: l.title }),
3636
+ /* @__PURE__ */ jsx(DialogDescription, { children: l.description })
3637
+ ] }),
3638
+ /* @__PURE__ */ jsxs(Tabs, { value: tab, onValueChange: (v) => setTab(v), children: [
3639
+ /* @__PURE__ */ jsxs(TabsList, { children: [
3640
+ /* @__PURE__ */ jsx(TabsTrigger, { value: "this", "data-testid": "docx-cross-reference-this", children: l.thisDocument }),
3641
+ references && /* @__PURE__ */ jsx(TabsTrigger, { value: "other", "data-testid": "docx-cross-reference-other", children: l.otherDocument })
3642
+ ] }),
3643
+ /* @__PURE__ */ jsxs(TabsContent, { value: "this", className: "space-y-3", children: [
3644
+ /* @__PURE__ */ jsx(
3645
+ Input,
3646
+ {
3647
+ value: query,
3648
+ placeholder: l.filterTargets,
3649
+ "aria-label": l.filterTargets,
3650
+ onChange: (e) => setQuery(e.target.value),
3651
+ "data-testid": "docx-cross-reference-filter"
3652
+ }
3653
+ ),
3654
+ /* @__PURE__ */ jsxs(
3655
+ "div",
3656
+ {
3657
+ role: "listbox",
3658
+ "aria-label": l.filterTargets,
3659
+ className: "max-h-64 space-y-0.5 overflow-y-auto rounded-md border border-border p-1",
3660
+ children: [
3661
+ shown.length === 0 && /* @__PURE__ */ jsx("p", { className: "p-2 text-sm text-muted-foreground", children: l.noTargets }),
3662
+ shown.map((t) => /* @__PURE__ */ jsxs(
3663
+ "button",
3664
+ {
3665
+ type: "button",
3666
+ role: "option",
3667
+ "aria-selected": chosen?.paragraphId === t.paragraphId,
3668
+ "data-testid": "docx-cross-reference-target",
3669
+ onClick: () => setChosen(t),
3670
+ onDoubleClick: () => onInsertTarget(t, t.number ? mode : "text"),
3671
+ className: cn(
3672
+ "flex w-full items-baseline gap-2 rounded px-2 py-1 text-left text-sm",
3673
+ chosen?.paragraphId === t.paragraphId ? "bg-accent" : "hover:bg-accent/50"
3674
+ ),
3675
+ style: { paddingLeft: 8 + t.level * 16 },
3676
+ children: [
3677
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 font-mono text-xs text-muted-foreground", children: t.marker || l.heading }),
3678
+ /* @__PURE__ */ jsx("span", { className: cn("truncate", t.heading && "font-medium"), children: t.text })
3679
+ ]
3680
+ },
3681
+ t.paragraphId
3682
+ ))
3683
+ ]
3684
+ }
3685
+ ),
3686
+ chosen && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3 text-sm", children: [
3687
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: l.insertAs }),
3688
+ chosen.number && /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-1", children: [
3689
+ /* @__PURE__ */ jsx(
3690
+ "input",
3691
+ {
3692
+ type: "radio",
3693
+ name: "cross-reference-mode",
3694
+ checked: effectiveMode === "number",
3695
+ onChange: () => setMode("number")
3696
+ }
3697
+ ),
3698
+ l.number
3699
+ ] }),
3700
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-1", children: [
3701
+ /* @__PURE__ */ jsx(
3702
+ "input",
3703
+ {
3704
+ type: "radio",
3705
+ name: "cross-reference-mode",
3706
+ checked: effectiveMode === "text",
3707
+ onChange: () => setMode("text")
3708
+ }
3709
+ ),
3710
+ l.text
3711
+ ] }),
3712
+ /* @__PURE__ */ jsx(
3713
+ "span",
3714
+ {
3715
+ className: "ml-auto rounded bg-muted px-2 py-0.5",
3716
+ "data-testid": "docx-cross-reference-preview",
3717
+ children: refResult(chosen, effectiveMode)
3718
+ }
3719
+ )
3720
+ ] })
3721
+ ] }),
3722
+ references && /* @__PURE__ */ jsxs(TabsContent, { value: "other", className: "space-y-3", children: [
3723
+ references.renderDocumentPicker ? references.renderDocumentPicker(chooseDocument, docId) : /* @__PURE__ */ jsxs(
3724
+ "select",
3725
+ {
3726
+ className: "h-9 w-full rounded-md border border-border bg-background px-2 text-sm",
3727
+ "aria-label": l.chooseDocument,
3728
+ value: docId ?? "",
3729
+ onChange: (e) => e.target.value && chooseDocument(e.target.value),
3730
+ "data-testid": "docx-cross-reference-document",
3731
+ children: [
3732
+ /* @__PURE__ */ jsx("option", { value: "", children: l.chooseDocument }),
3733
+ (documents ?? []).map((d) => /* @__PURE__ */ jsx("option", { value: d.id, children: d.title }, d.id))
3734
+ ]
3735
+ }
3736
+ ),
3737
+ docId && /* @__PURE__ */ jsxs(Fragment, { children: [
3738
+ /* @__PURE__ */ jsx(
3739
+ Input,
3740
+ {
3741
+ value: passageQuery,
3742
+ placeholder: l.filterPassages,
3743
+ "aria-label": l.filterPassages,
3744
+ onChange: (e) => setPassageQuery(e.target.value),
3745
+ "data-testid": "docx-cross-reference-passage-filter"
3746
+ }
3747
+ ),
3748
+ /* @__PURE__ */ jsx(
3749
+ "div",
3750
+ {
3751
+ className: "max-h-64 space-y-1 overflow-y-auto",
3752
+ "data-testid": "docx-cross-reference-passages",
3753
+ children: list === "loading" || list === void 0 ? /* @__PURE__ */ jsx("p", { className: "p-2 text-sm text-muted-foreground", children: l.loadingPassages }) : shownPassages.length === 0 ? /* @__PURE__ */ jsx("p", { className: "p-2 text-sm text-muted-foreground", children: l.noPassages }) : shownPassages.map((p, i) => /* @__PURE__ */ jsx(
3754
+ FactPassageItem,
3755
+ {
3756
+ index: i,
3757
+ page: p.page,
3758
+ excerpt: passageExcerpt(p.text, 200),
3759
+ isViewing: passage?.chunkId === p.chunkId,
3760
+ onSelect: () => {
3761
+ setPassage(p);
3762
+ setText(referenceText(title, p));
3763
+ },
3764
+ testId: "docx-cross-reference-passage"
3765
+ },
3766
+ p.chunkId
3767
+ ))
3768
+ }
3769
+ ),
3770
+ passage && /* @__PURE__ */ jsxs("label", { className: "block space-y-1 text-sm", children: [
3771
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: l.referenceText }),
3772
+ /* @__PURE__ */ jsx(
3773
+ Input,
3774
+ {
3775
+ value: text,
3776
+ onChange: (e) => setText(e.target.value),
3777
+ "data-testid": "docx-cross-reference-text"
3778
+ }
3779
+ )
3780
+ ] })
3781
+ ] })
3782
+ ] })
3783
+ ] }),
3784
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
3785
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: onCancel, children: l.cancel }),
3786
+ /* @__PURE__ */ jsx(Button, { disabled: !canInsert, onClick: insert, "data-testid": "docx-cross-reference-insert", children: l.insertButton })
3787
+ ] })
3788
+ ]
3789
+ }
3790
+ ) });
3969
3791
  }
3970
3792
  function CrossReferenceButton() {
3971
3793
  const references = useCrossReferences();
@@ -4632,6 +4454,186 @@ function LayoutTabContent({ tab }) {
4632
4454
  if (tab === PICTURE_TAB) return /* @__PURE__ */ jsx(PictureButtons, {});
4633
4455
  return null;
4634
4456
  }
4457
+ var titleOf = (prompt) => {
4458
+ const line = prompt.trim().split("\n")[0];
4459
+ return line.length > 80 ? `${line.slice(0, 79)}\u2026` : line;
4460
+ };
4461
+ function rememberCaret(editor) {
4462
+ const surface = editor.surface;
4463
+ const head = surface?.state().selection?.head;
4464
+ if (!surface || !head) return null;
4465
+ return { paraId: surface.session.paraIdOf(head.paragraphId), ...head };
4466
+ }
4467
+ var mounted = [];
4468
+ var running = 0;
4469
+ var listeners = /* @__PURE__ */ new Set();
4470
+ var setRunning = (delta) => {
4471
+ running += delta;
4472
+ listeners.forEach((listener) => listener());
4473
+ };
4474
+ var subscribe = (listener) => {
4475
+ listeners.add(listener);
4476
+ return () => void listeners.delete(listener);
4477
+ };
4478
+ function insertionTarget(origin, caret, editors = mounted) {
4479
+ if (editors.includes(origin) && origin.surface) return origin;
4480
+ const paraId = caret?.paraId;
4481
+ if (!paraId) return null;
4482
+ for (let i = editors.length - 1; i >= 0; i--) {
4483
+ const session = editors[i].surface?.session;
4484
+ if (session?.nodeIdOf(paraId)) return editors[i];
4485
+ }
4486
+ return null;
4487
+ }
4488
+ function restoreCaret(editor, caret) {
4489
+ const surface = editor.surface;
4490
+ if (!surface || !caret) return;
4491
+ const paragraphId = caret.paraId && surface.session.nodeIdOf(caret.paraId) || caret.paragraphId;
4492
+ if (!surface.session.paragraphIds().includes(paragraphId)) return;
4493
+ const point = { paragraphId, offset: caret.offset };
4494
+ surface.setSelection({ anchor: point, head: point });
4495
+ }
4496
+ async function insertGeneratedImage(editor, bytes, prompt, caret) {
4497
+ const image = normalizeImageBytes(bytes);
4498
+ if (!image.ok) throw new Error("The generated image is not in a format Word can embed.");
4499
+ let result;
4500
+ for (let attempt = 0; attempt < 3 && !result?.ok; attempt++) {
4501
+ editor.focus();
4502
+ restoreCaret(editor, caret);
4503
+ result = await executeImageCommand(editor, {
4504
+ type: "insertImage",
4505
+ data: image.bytes,
4506
+ mime: image.mime,
4507
+ widthPoints: image.widthPoints,
4508
+ heightPoints: image.heightPoints,
4509
+ description: prompt.trim(),
4510
+ title: titleOf(prompt)
4511
+ });
4512
+ if (!result.ok && result.reason !== "stale drawing selection") break;
4513
+ }
4514
+ if (!result?.ok) throw new Error(result?.reason || "The picture could not be inserted here.");
4515
+ }
4516
+ var AUTO = "";
4517
+ function useGenerateImageTarget() {
4518
+ const editor = useDocxEditor();
4519
+ const loaded = useEditorState((snapshot) => !snapshot.isLoading);
4520
+ useEffect(() => {
4521
+ if (!editor || !loaded) return;
4522
+ mounted.push(editor);
4523
+ return () => void mounted.splice(mounted.indexOf(editor), 1);
4524
+ }, [editor, loaded]);
4525
+ }
4526
+ function GenerateImageButton({ generation }) {
4527
+ const editor = useDocxEditor();
4528
+ const [prompt, setPrompt] = useState("");
4529
+ const [aspectRatio, setAspectRatio] = useState(AUTO);
4530
+ const [busy, setBusy] = useState(false);
4531
+ const [error, setError] = useState("");
4532
+ const abort = useRef(null);
4533
+ const close = useRef(() => {
4534
+ });
4535
+ const editable = useEditorState((snapshot) => snapshot.editable);
4536
+ const generating = useSyncExternalStore(subscribe, () => running > 0);
4537
+ async function run2() {
4538
+ if (!editor || !prompt.trim() || busy) return;
4539
+ const caret = rememberCaret(editor);
4540
+ const controller = new AbortController();
4541
+ abort.current = controller;
4542
+ setBusy(true);
4543
+ setRunning(1);
4544
+ setError("");
4545
+ try {
4546
+ const bytes = await generation.generate(prompt.trim(), {
4547
+ aspectRatio: aspectRatio || void 0,
4548
+ signal: controller.signal
4549
+ });
4550
+ if (controller.signal.aborted) return;
4551
+ const target = insertionTarget(editor, caret);
4552
+ if (!target) throw new Error("The document was closed before the generated image was ready.");
4553
+ await insertGeneratedImage(target, bytes, prompt, caret);
4554
+ setPrompt("");
4555
+ close.current();
4556
+ generation.notify?.("Generated image inserted", "success");
4557
+ } catch (failure) {
4558
+ if (controller.signal.aborted) return;
4559
+ const message = failure instanceof Error ? failure.message : String(failure);
4560
+ setError(message);
4561
+ generation.notify?.(message, "error");
4562
+ } finally {
4563
+ abort.current = null;
4564
+ setRunning(-1);
4565
+ setBusy(false);
4566
+ }
4567
+ }
4568
+ if (!editable && !generating) return null;
4569
+ return /* @__PURE__ */ jsx(
4570
+ RibbonMenu,
4571
+ {
4572
+ label: generating ? "Generating image\u2026" : "Generate image",
4573
+ icon: generating ? /* @__PURE__ */ jsx(Loader2, { size: 16, className: "native-docx-spin" }) : /* @__PURE__ */ jsx(Sparkles, { size: 16 }),
4574
+ testId: "docx-generate-image",
4575
+ children: (dismiss) => {
4576
+ close.current = dismiss;
4577
+ return /* @__PURE__ */ jsxs(
4578
+ "form",
4579
+ {
4580
+ className: "native-docx-form native-docx-generate-image",
4581
+ onSubmit: (event) => {
4582
+ event.preventDefault();
4583
+ void run2();
4584
+ },
4585
+ children: [
4586
+ /* @__PURE__ */ jsxs("label", { children: [
4587
+ "Describe the image",
4588
+ /* @__PURE__ */ jsx(
4589
+ "textarea",
4590
+ {
4591
+ autoFocus: true,
4592
+ rows: 3,
4593
+ value: prompt,
4594
+ disabled: busy,
4595
+ placeholder: "A watercolour of a lighthouse at dawn",
4596
+ "data-testid": "docx-generate-image-prompt",
4597
+ onChange: (event) => setPrompt(event.target.value),
4598
+ onKeyDown: (event) => {
4599
+ if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) void run2();
4600
+ }
4601
+ }
4602
+ )
4603
+ ] }),
4604
+ generation.aspectRatios && generation.aspectRatios.length > 0 && /* @__PURE__ */ jsxs("label", { children: [
4605
+ "Aspect ratio",
4606
+ /* @__PURE__ */ jsxs(
4607
+ "select",
4608
+ {
4609
+ value: aspectRatio,
4610
+ disabled: busy,
4611
+ onChange: (event) => setAspectRatio(event.target.value),
4612
+ children: [
4613
+ /* @__PURE__ */ jsx("option", { value: AUTO, children: "Auto" }),
4614
+ generation.aspectRatios.map((ratio) => /* @__PURE__ */ jsx("option", { value: ratio.value, children: ratio.label }, ratio.value))
4615
+ ]
4616
+ }
4617
+ )
4618
+ ] }),
4619
+ busy && /* @__PURE__ */ jsx("p", { role: "status", "data-testid": "docx-generate-image-status", children: "Generating\u2026 the picture is inserted where your cursor was. You can keep writing." }),
4620
+ error && /* @__PURE__ */ jsx("p", { role: "alert", children: error }),
4621
+ /* @__PURE__ */ jsx("div", { className: "native-docx-form-actions", children: busy ? /* @__PURE__ */ jsx("button", { type: "button", onClick: () => abort.current?.abort(), children: "Cancel" }) : /* @__PURE__ */ jsx(
4622
+ "button",
4623
+ {
4624
+ type: "submit",
4625
+ disabled: !prompt.trim(),
4626
+ "data-testid": "docx-generate-image-submit",
4627
+ children: "Generate"
4628
+ }
4629
+ ) })
4630
+ ]
4631
+ }
4632
+ );
4633
+ }
4634
+ }
4635
+ );
4636
+ }
4635
4637
  var contains = (node, id) => node.id === id || (node.children ?? []).some((child) => contains(child, id));
4636
4638
  function firstParagraph(node) {
4637
4639
  for (const child of node.children ?? []) {
@@ -4927,6 +4929,19 @@ function DocxRibbon({
4927
4929
  badge && /* @__PURE__ */ jsx("span", { className: "native-docx-ribbon-badge", "data-testid": "docx-ribbon-badge", children: badge })
4928
4930
  ] });
4929
4931
  }
4932
+ function CompactToolbar() {
4933
+ const t = useChromeTranslate();
4934
+ return /* @__PURE__ */ jsxs(DocxEditor.Toolbar, { t, preset: false, children: [
4935
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.Undo, {}),
4936
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.Redo, {}),
4937
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.Bold, {}),
4938
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.Italic, {}),
4939
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.Underline, {}),
4940
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.BulletList, {}),
4941
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.NumberedList, {}),
4942
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.Link, {})
4943
+ ] });
4944
+ }
4930
4945
  var fixedZoomMode = { type: "fixed" };
4931
4946
  function NativeDocxEditor({
4932
4947
  documentId,
@@ -4936,7 +4951,9 @@ function NativeDocxEditor({
4936
4951
  snapshot = false,
4937
4952
  onSaved,
4938
4953
  stream,
4939
- adapter
4954
+ adapter,
4955
+ compact = false,
4956
+ stateRef
4940
4957
  }) {
4941
4958
  const [ready, setReady] = useState(), [error, setError] = useState(""), [retry, setRetry] = useState(0);
4942
4959
  const [status, setStatus] = useState("Connecting\u2026"), [dirty, setDirty] = useState(false), [saving, setSaving] = useState(false), [offline, setOffline] = useState(false);
@@ -5107,6 +5124,14 @@ function NativeDocxEditor({
5107
5124
  room?.destroy();
5108
5125
  };
5109
5126
  }, [documentId, isDraft, snapshot, retry]);
5127
+ useEffect(() => {
5128
+ if (!stateRef) return;
5129
+ const room = ready?.room;
5130
+ stateRef.current = () => room ? encodeState(room) : void 0;
5131
+ return () => {
5132
+ stateRef.current = null;
5133
+ };
5134
+ }, [stateRef, ready?.room]);
5110
5135
  useEffect(() => {
5111
5136
  adapter.onDirtyChange?.(dirty);
5112
5137
  const beforeUnload = (e) => {
@@ -5190,7 +5215,7 @@ function NativeDocxEditor({
5190
5215
  if (!pageWidth) return;
5191
5216
  const style = getComputedStyle(viewport);
5192
5217
  const available = viewport.clientWidth - parseFloat(style.paddingLeft) - parseFloat(style.paddingRight);
5193
- const zoom = Math.max(0.1, Math.min(2.5, available / pageWidth));
5218
+ const zoom = Math.max(0.1, Math.min(compact ? 1 : 2.5, available / pageWidth));
5194
5219
  if (Math.abs(editor.getZoom() - zoom) > 1e-3) editor.setZoom(zoom);
5195
5220
  };
5196
5221
  const resize = new ResizeObserver(fit);
@@ -5226,7 +5251,7 @@ function NativeDocxEditor({
5226
5251
  return /* @__PURE__ */ jsxs(
5227
5252
  "section",
5228
5253
  {
5229
- className: "native-docx-viewer",
5254
+ className: compact ? "native-docx-viewer native-docx-compact" : "native-docx-viewer",
5230
5255
  "data-testid": "native-docx-editor",
5231
5256
  "aria-busy": !!stream?.busy,
5232
5257
  "data-generation-state": stream ? stream.busy ? "generating" : stream.error ? "stopped" : "generated" : "ready",
@@ -5255,7 +5280,7 @@ function NativeDocxEditor({
5255
5280
  document: ready.document,
5256
5281
  modules: ready.modules,
5257
5282
  fonts: ready.fonts,
5258
- mode: stream || isDraft && ready.canEdit && !snapshot ? "edit" : !ready.canEdit || snapshot ? "view" : "suggesting",
5283
+ mode: stream || isDraft && ready.canEdit && !snapshot || compact && ready.canEdit ? "edit" : !ready.canEdit || snapshot ? "view" : "suggesting",
5259
5284
  reviewDisplayMode: "all-markup",
5260
5285
  zoomMode: fixedZoomMode,
5261
5286
  author: ready.author,
@@ -5296,7 +5321,8 @@ function NativeDocxEditor({
5296
5321
  trackMoves: true
5297
5322
  }
5298
5323
  ),
5299
- !stream && /* @__PURE__ */ jsx(
5324
+ compact && !stream && /* @__PURE__ */ jsx(CompactToolbar, {}),
5325
+ !compact && !stream && /* @__PURE__ */ jsx(
5300
5326
  DocxRibbon,
5301
5327
  {
5302
5328
  onSave: () => void saveVersion(),
@@ -5335,13 +5361,13 @@ function NativeDocxEditor({
5335
5361
  children: /* @__PURE__ */ jsx(DocxEditorReview, { children: () => /* @__PURE__ */ jsx(DocxEditorReview.Card, { children: /* @__PURE__ */ jsx(DocxCommentActivity, {}) }) })
5336
5362
  }
5337
5363
  ),
5338
- /* @__PURE__ */ jsx(DocxEditor.HeaderFooterChrome, {}),
5339
- /* @__PURE__ */ jsx(DocxEditor.NotesChrome, {}),
5364
+ !compact && /* @__PURE__ */ jsx(DocxEditor.HeaderFooterChrome, {}),
5365
+ !compact && /* @__PURE__ */ jsx(DocxEditor.NotesChrome, {}),
5340
5366
  /* @__PURE__ */ jsx(DocxEditor.Equation, {}),
5341
5367
  /* @__PURE__ */ jsx(DocxEditor.HyperLink, {}),
5342
5368
  /* @__PURE__ */ jsx(DocxEditor.ContextMenu, { children: /* @__PURE__ */ jsx(DocxContextMenuExtras, {}) })
5343
5369
  ] }),
5344
- /* @__PURE__ */ jsx(DocxEditor.PageNumber, {})
5370
+ !compact && /* @__PURE__ */ jsx(DocxEditor.PageNumber, {})
5345
5371
  ]
5346
5372
  }
5347
5373
  )
@@ -5360,11 +5386,19 @@ function NativeDocxEditor({
5360
5386
  status
5361
5387
  ] }),
5362
5388
  /* @__PURE__ */ jsx(DocxParticipants, { session: ready?.room?.session }),
5363
- /* @__PURE__ */ jsxs("button", { onClick: () => void exportDocument(), disabled: !ready, "aria-label": "Download DOCX", children: [
5364
- /* @__PURE__ */ jsx(Download, { size: 14 }),
5365
- "DOCX"
5366
- ] }),
5367
- !snapshot && !stream && ready?.canEdit && /* @__PURE__ */ jsxs(
5389
+ !compact && /* @__PURE__ */ jsxs(
5390
+ "button",
5391
+ {
5392
+ onClick: () => void exportDocument(),
5393
+ disabled: !ready,
5394
+ "aria-label": "Download DOCX",
5395
+ children: [
5396
+ /* @__PURE__ */ jsx(Download, { size: 14 }),
5397
+ "DOCX"
5398
+ ]
5399
+ }
5400
+ ),
5401
+ !compact && !snapshot && !stream && ready?.canEdit && /* @__PURE__ */ jsxs(
5368
5402
  "button",
5369
5403
  {
5370
5404
  className: "native-docx-save",
@@ -5382,70 +5416,7 @@ function NativeDocxEditor({
5382
5416
  }
5383
5417
  );
5384
5418
  }
5385
- var loaded = /* @__PURE__ */ new Map();
5386
- function loadDocxFonts(baseUrl = "/native-docx/fonts") {
5387
- let promise = loaded.get(baseUrl);
5388
- if (!promise) {
5389
- promise = loadFonts(baseUrl).catch((error) => {
5390
- loaded.delete(baseUrl);
5391
- throw error;
5392
- });
5393
- loaded.set(baseUrl, promise);
5394
- }
5395
- return promise;
5396
- }
5397
- async function loadFonts(baseUrl) {
5398
- const families = ["Liberation Serif", "Liberation Sans", "Liberation Mono"];
5399
- const variants = [
5400
- { suffix: "Regular", weight: 400, style: "normal" },
5401
- { suffix: "Bold", weight: 700, style: "normal" },
5402
- { suffix: "Italic", weight: 400, style: "italic" },
5403
- { suffix: "BoldItalic", weight: 700, style: "italic" }
5404
- ];
5405
- const sources = await Promise.all(
5406
- families.flatMap(
5407
- (family) => variants.map(async (face) => {
5408
- const r = await fetch(
5409
- `${baseUrl.replace(/\/$/, "")}/${family.replace(" ", "")}-${face.suffix}.ttf`
5410
- );
5411
- if (!r.ok) throw new Error(`Could not load the document font ${family}.`);
5412
- const result = createFontSource(new Uint8Array(await r.arrayBuffer()), {
5413
- family,
5414
- weight: face.weight,
5415
- style: face.style
5416
- });
5417
- if ("failure" in result)
5418
- throw new Error(`Could not initialize the document font ${family}.`);
5419
- return result.source;
5420
- })
5421
- )
5422
- );
5423
- const aliases = {
5424
- "Times New Roman": "Liberation Serif",
5425
- Cambria: "Liberation Serif",
5426
- "DejaVu Serif": "Liberation Serif",
5427
- Calibri: "Liberation Sans",
5428
- Aptos: "Liberation Sans",
5429
- Arial: "Liberation Sans",
5430
- Helvetica: "Liberation Sans",
5431
- "Courier New": "Liberation Mono",
5432
- "DejaVu Sans Mono": "Liberation Mono"
5433
- };
5434
- return {
5435
- epoch: 1,
5436
- maxFontBytes: 12 * 1024 * 1024,
5437
- sources,
5438
- defaultFont: { family: "Liberation Serif", sizeHalfPoints: 22 },
5439
- supportedFamilies: [...families, ...Object.keys(aliases)],
5440
- substitutions: Object.entries(aliases).flatMap(
5441
- ([from, to]) => variants.map((face) => ({
5442
- from: { family: from, weight: face.weight, style: face.style },
5443
- to: { family: to, weight: face.weight, style: face.style }
5444
- }))
5445
- )
5446
- };
5447
- }
5448
5419
 
5449
- export { BUILTIN_TEMPLATE_NAME, CrossReferenceDialog, CrossReferencesProvider, DEFAULT_CROSS_REFERENCES_LABELS, DEFAULT_DEFINED_TERMS_LABELS, DEFINED_TERM_TRIGGER, DefinedTermsProvider, DocxAuthorContext, DocxCommentActivity, DocxEditorReview, DocxMentions, DocxParticipants, DocxRibbon, GenerateImageButton, HouseTemplateProvider, LinkHoverCard, NativeDocxEditor, builtinHouseTemplate, contentPath, editorPort, findDefinitionsSection, insertCrossReference, insertDefinedTerm, insertDocumentReference, listTargets, loadDocxFonts, normalizeDocxExport, paragraphNumber, referenceText, referencesPort, resolveHouseTemplate, reviewModule, shortTitle, structuralPort, templateFileName, trackedOrNoted, useCrossReferences, useDefinedTerms, useHouseTemplate, useReview, useReviewItem, useTrackedOrNoted, withDirectEditing };
5450
- //# sourceMappingURL=chunk-W2RTZOK3.js.map
5451
- //# sourceMappingURL=chunk-W2RTZOK3.js.map
5420
+ export { BUILTIN_TEMPLATE_NAME, CrossReferenceDialog, CrossReferencesProvider, DEFAULT_CROSS_REFERENCES_LABELS, DEFAULT_DEFINED_TERMS_LABELS, DEFINED_TERM_TRIGGER, DefinedTermsProvider, DocxAuthorContext, DocxCommentActivity, DocxEditorReview, DocxMentions, DocxParticipants, DocxRibbon, GenerateImageButton, HouseTemplateProvider, LinkHoverCard, NativeDocxEditor, builtinHouseTemplate, contentPath, editorPort, findDefinitionsSection, insertCrossReference, insertDefinedTerm, insertDocumentReference, listTargets, normalizeDocxExport, paragraphNumber, referenceText, referencesPort, resolveHouseTemplate, reviewModule, shortTitle, structuralPort, templateFileName, trackedOrNoted, useCrossReferences, useDefinedTerms, useHouseTemplate, useReview, useReviewItem, useTrackedOrNoted, withDirectEditing };
5421
+ //# sourceMappingURL=chunk-2SJTXYRM.js.map
5422
+ //# sourceMappingURL=chunk-2SJTXYRM.js.map