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