@arbidocs/blocks 0.4.97 → 0.4.99

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 (35) hide show
  1. package/dist/{chunk-RSUOOMPT.js → chunk-3ICWR3BK.js} +3 -3
  2. package/dist/{chunk-RSUOOMPT.js.map → chunk-3ICWR3BK.js.map} +1 -1
  3. package/dist/{chunk-HGJGALLN.cjs → chunk-44TPPPRJ.cjs} +462 -83
  4. package/dist/chunk-44TPPPRJ.cjs.map +1 -0
  5. package/dist/{chunk-QIEEFBZ5.js → chunk-4ZTIMQ7Z.js} +453 -85
  6. package/dist/chunk-4ZTIMQ7Z.js.map +1 -0
  7. package/dist/{chunk-YH6SSJDW.js → chunk-C5RF47LD.js} +3 -3
  8. package/dist/{chunk-YH6SSJDW.js.map → chunk-C5RF47LD.js.map} +1 -1
  9. package/dist/{chunk-EHB7J6SR.cjs → chunk-MABEOI4S.cjs} +5 -5
  10. package/dist/{chunk-EHB7J6SR.cjs.map → chunk-MABEOI4S.cjs.map} +1 -1
  11. package/dist/{chunk-GVOTXXZG.cjs → chunk-QBEUCOC7.cjs} +5 -5
  12. package/dist/{chunk-GVOTXXZG.cjs.map → chunk-QBEUCOC7.cjs.map} +1 -1
  13. package/dist/connect/docx.cjs +6 -6
  14. package/dist/connect/docx.js +4 -4
  15. package/dist/connect/index.cjs +3 -3
  16. package/dist/connect/index.js +2 -2
  17. package/dist/docx/NativeDocxEditor.d.ts +7 -0
  18. package/dist/docx/RibbonMenu.d.ts +3 -1
  19. package/dist/docx/definitions/fragment.d.ts +3 -1
  20. package/dist/docx/definitions/port.d.ts +6 -2
  21. package/dist/docx/index.cjs +68 -24
  22. package/dist/docx/index.d.ts +4 -0
  23. package/dist/docx/index.js +3 -3
  24. package/dist/docx/structural/trackedOrNoted.d.ts +51 -0
  25. package/dist/docx/structural/useTrackedOrNoted.d.ts +5 -0
  26. package/dist/docx/template/HouseTemplate.d.ts +64 -0
  27. package/dist/docx/template/HouseTemplatePanel.d.ts +6 -0
  28. package/dist/docx/template/QuickParts.d.ts +7 -0
  29. package/dist/docx/wordStyles.d.ts +3 -0
  30. package/dist/index.cjs +46 -46
  31. package/dist/index.js +3 -3
  32. package/package.json +4 -4
  33. package/src/docx/styles.css +102 -0
  34. package/dist/chunk-HGJGALLN.cjs.map +0 -1
  35. package/dist/chunk-QIEEFBZ5.js.map +0 -1
@@ -11,6 +11,7 @@ var ui = require('@arbidocs/react/ui');
11
11
  var lucideReact = require('lucide-react');
12
12
  var jsxRuntime = require('react/jsx-runtime');
13
13
  var reactDom = require('react-dom');
14
+ var template = require('@arbidocs/coedit/template');
14
15
  var server = require('react-dom/server');
15
16
  var Y = require('yjs');
16
17
  var coedit = require('@arbidocs/coedit');
@@ -1284,8 +1285,6 @@ function DocxMentions({
1284
1285
  }
1285
1286
  ) });
1286
1287
  }
1287
-
1288
- // src/docx/definitions/fragment.ts
1289
1288
  var DEFINED_TERM_STYLE = "DefinedTerm";
1290
1289
  var FRAGMENT_STYLES = chunkAOFATOT3_cjs.stylesXml(
1291
1290
  "",
@@ -1318,12 +1317,13 @@ function base64(bytes) {
1318
1317
  binary += String.fromCharCode(...bytes.subarray(i, i + 32768));
1319
1318
  return btoa(binary);
1320
1319
  }
1321
- function fragmentHtml(body, covered2) {
1322
- const bytes = chunkAOFATOT3_cjs.docxPackage({
1320
+ function fragmentHtml(body, covered2, revision) {
1321
+ const untracked = chunkAOFATOT3_cjs.docxPackage({
1323
1322
  document: chunkAOFATOT3_cjs.documentXml(body),
1324
1323
  styles: FRAGMENT_STYLES,
1325
1324
  title: "Defined terms"
1326
1325
  });
1326
+ const bytes = revision ? template.trackInsertion(untracked, revision, covered2) : untracked;
1327
1327
  return `<div data-docx-fragment="${base64(bytes)}" data-docx-fragment-end="${covered2 ? "covered" : "open"}"></div>`;
1328
1328
  }
1329
1329
 
@@ -1580,7 +1580,7 @@ function readParagraph(node, topLevel, bookmarks) {
1580
1580
  links
1581
1581
  };
1582
1582
  }
1583
- function editorPort(editor) {
1583
+ function editorPort(editor, revision) {
1584
1584
  const surface = () => {
1585
1585
  const s = editor.surface;
1586
1586
  if (!s) throw new Error("The document is not open.");
@@ -1599,7 +1599,7 @@ function editorPort(editor) {
1599
1599
  surface().setSelection({ anchor: from, head: to });
1600
1600
  surface().deleteSelection();
1601
1601
  },
1602
- paste: (body, text, covered2) => editor.exec({ type: "paste", text, html: fragmentHtml(body, covered2) }).ok,
1602
+ paste: (body, text, covered2) => editor.exec({ type: "paste", text, html: fragmentHtml(body, covered2, revision) }).ok,
1603
1603
  splitParagraph: () => surface().splitParagraph()
1604
1604
  };
1605
1605
  }
@@ -1642,6 +1642,70 @@ function holdCaret(editor, timeoutMs = 5e3) {
1642
1642
  const offSelection = editor.on("selectionChange", restore);
1643
1643
  const timer = setTimeout(stop, timeoutMs);
1644
1644
  }
1645
+
1646
+ // src/docx/structural/trackedOrNoted.ts
1647
+ var revisionDate = (at = /* @__PURE__ */ new Date()) => at.toISOString().replace(/\.\d{3}Z$/, "Z");
1648
+ function trackedOrNoted(port, { label, author, insert, inserted = Boolean }) {
1649
+ const mode = port.mode();
1650
+ if (mode === "viewing") throw new Error("This document is read only.");
1651
+ if (mode !== "suggesting") return { result: insert(null), how: "direct" };
1652
+ const revision = { author, date: revisionDate() };
1653
+ const before = port.caret();
1654
+ const tracked = port.trackedBy(revision);
1655
+ port.setMode("editing");
1656
+ let result;
1657
+ try {
1658
+ result = insert(revision);
1659
+ } finally {
1660
+ port.setMode("suggesting");
1661
+ }
1662
+ if (!inserted(result) || port.trackedBy(revision) > tracked)
1663
+ return { result, how: inserted(result) ? "tracked" : "direct" };
1664
+ const after = port.caret();
1665
+ if (before && after && port.comment(before, after, label)) return { result, how: "noted" };
1666
+ return { result, how: "direct" };
1667
+ }
1668
+ var attr2 = (node, name) => node.attributes?.find((a) => a.localName === name)?.value;
1669
+ function structuralPort(editor) {
1670
+ return {
1671
+ mode: () => editor.getEditingMode(),
1672
+ setMode: (mode) => void editor.setEditingMode(mode),
1673
+ caret: () => {
1674
+ const head = editor.surface?.state().selection?.head;
1675
+ return head ? { paragraphId: head.paragraphId, offset: head.offset } : null;
1676
+ },
1677
+ // Read from the story's tree: the review pane merges adjacent insertions by one author into
1678
+ // one card, so a card's date need not be this insert's.
1679
+ trackedBy: ({ author, date }) => {
1680
+ const root = editor.surface?.session.part().root;
1681
+ let count = 0;
1682
+ const visit = (node) => {
1683
+ if (node.localName === "ins" && attr2(node, "author") === author && attr2(node, "date") === date)
1684
+ count++;
1685
+ node.children?.forEach(visit);
1686
+ };
1687
+ if (root) visit(root);
1688
+ return count;
1689
+ },
1690
+ comment: (from, to, text) => {
1691
+ const surface = editor.surface;
1692
+ if (!surface) return false;
1693
+ surface.setSelection({ anchor: from, head: to });
1694
+ const ok2 = editor.addComment(text).ok;
1695
+ surface.setSelection({ anchor: to, head: to });
1696
+ return ok2;
1697
+ }
1698
+ };
1699
+ }
1700
+ var DocxAuthorContext = React3.createContext("You");
1701
+ function useTrackedOrNoted() {
1702
+ const editor = react.useDocxEditor();
1703
+ const author = React3.useContext(DocxAuthorContext);
1704
+ return React3.useCallback(
1705
+ (spec) => editor ? trackedOrNoted(structuralPort(editor), { ...spec, author }) : void 0,
1706
+ [editor, author]
1707
+ );
1708
+ }
1645
1709
  var DEFINED_TERM_TRIGGER = "@@";
1646
1710
  var TRIGGER_QUERY = /(?:^|[\s([{"“‘'])@@([^@\n\t]{0,40})$/;
1647
1711
  var MAX_GROUPS = 8;
@@ -1685,6 +1749,7 @@ function DefinedTermsProvider({
1685
1749
  labels
1686
1750
  }) {
1687
1751
  const editor = react.useDocxEditor();
1752
+ const track = useTrackedOrNoted();
1688
1753
  const l = React3.useMemo(() => ({ ...DEFAULT_DEFINED_TERMS_LABELS, ...labels }), [labels]);
1689
1754
  const [sources, setSources] = React3.useState(null);
1690
1755
  const loadedAt = React3.useRef(0);
@@ -1741,35 +1806,46 @@ function DefinedTermsProvider({
1741
1806
  refreshRef.current = refresh;
1742
1807
  const firstHover = React3.useCallback(() => void refreshRef.current(), []);
1743
1808
  const caretRect = () => container().querySelector(".docx-editor-one-surface__caret")?.getBoundingClientRect();
1744
- const run = React3.useCallback(
1809
+ const attempt = React3.useCallback(
1745
1810
  (fn) => {
1746
- if (!editor) return void 0;
1747
1811
  try {
1748
- return withDirectEditing(editor, fn);
1812
+ return fn();
1749
1813
  } catch (error) {
1750
1814
  notify?.(error instanceof Error ? error.message : "Could not change the document.", "error");
1751
1815
  return void 0;
1752
1816
  }
1753
1817
  },
1754
- [editor, notify]
1818
+ [notify]
1819
+ );
1820
+ const run = React3.useCallback(
1821
+ (fn) => editor ? attempt(() => withDirectEditing(editor, fn)) : void 0,
1822
+ [editor, attempt]
1755
1823
  );
1756
1824
  const pick = React3.useCallback(
1757
1825
  (row) => {
1758
1826
  if (!editor || !picker || !row) return;
1759
1827
  const port = editorPort(editor);
1760
- const result = run(() => {
1828
+ const placed = run(() => {
1761
1829
  if (picker.mode === "search" && picker.caret) port.setCaret(picker.caret);
1762
- return insertDefinedTerm(port, row.definition, {
1763
- replace: picker.mode === "typing" ? DEFINED_TERM_TRIGGER.length + picker.query.length : 0
1764
- });
1830
+ const caret = port.caret();
1831
+ if (!caret) throw new Error("Place the cursor where the term should go.");
1832
+ const typed = picker.mode === "typing" ? DEFINED_TERM_TRIGGER.length + picker.query.length : 0;
1833
+ if (typed) port.deleteRange({ ...caret, offset: Math.max(0, caret.offset - typed) }, caret);
1834
+ return true;
1765
1835
  });
1836
+ const result = placed ? attempt(
1837
+ () => track({
1838
+ label: `Inserted defined term: ${row.definition.term}`,
1839
+ insert: (revision) => insertDefinedTerm(editorPort(editor, revision), row.definition)
1840
+ })?.result
1841
+ ) : void 0;
1766
1842
  setPicker(null);
1767
1843
  editor.surface?.focus();
1768
1844
  if (result) holdCaret(editor);
1769
1845
  if (result?.addedDefinition)
1770
1846
  notify?.(`\u201C${row.definition.term}\u201D added to the Definitions section.`, "success");
1771
1847
  },
1772
- [editor, picker, run, notify]
1848
+ [editor, picker, run, attempt, track, notify]
1773
1849
  );
1774
1850
  const openPicker = React3.useCallback(
1775
1851
  (anchor) => {
@@ -2147,6 +2223,138 @@ function DefinedTermHover({
2147
2223
  document.body
2148
2224
  );
2149
2225
  }
2226
+ var BUILTIN_TEMPLATE_NAME = "ARBI legal template";
2227
+ var templateFileName = (t) => /\.(dotx|dotm|docx|docm)$/i.test(t.name) ? t.name : `${t.name}.dotx`;
2228
+ var builtin;
2229
+ function builtinHouseTemplate() {
2230
+ builtin ??= { bytes: template.defaultLegalTemplate(), name: BUILTIN_TEMPLATE_NAME, source: "builtin" };
2231
+ return builtin;
2232
+ }
2233
+ async function resolveHouseTemplate(host) {
2234
+ if (!host) return builtinHouseTemplate();
2235
+ try {
2236
+ return await host.load();
2237
+ } catch {
2238
+ return builtinHouseTemplate();
2239
+ }
2240
+ }
2241
+ var HouseTemplateContext = React3.createContext(null);
2242
+ var useHouseTemplate = () => React3.useContext(HouseTemplateContext);
2243
+ function HouseTemplateProvider({
2244
+ host,
2245
+ documentId,
2246
+ editable,
2247
+ notify,
2248
+ enabled = true,
2249
+ children
2250
+ }) {
2251
+ const editor = react.useDocxEditor();
2252
+ const track = useTrackedOrNoted();
2253
+ const [template$1, setTemplate] = React3.useState(builtinHouseTemplate);
2254
+ const [divergence, setDivergence] = React3.useState(null);
2255
+ const [basedOnTemplate, setBasedOnTemplate] = React3.useState(false);
2256
+ const [levels, setLevels] = React3.useState({});
2257
+ const [generation, setGeneration] = React3.useState(0);
2258
+ React3.useEffect(() => {
2259
+ let live = true;
2260
+ void resolveHouseTemplate(host).then((t) => live && setTemplate(t));
2261
+ return () => {
2262
+ live = false;
2263
+ };
2264
+ }, [host]);
2265
+ const blocks = React3.useMemo(() => {
2266
+ try {
2267
+ return template.listBuildingBlocks(template$1.bytes);
2268
+ } catch {
2269
+ return [];
2270
+ }
2271
+ }, [template$1]);
2272
+ React3.useEffect(() => {
2273
+ if (!editor || !enabled) return;
2274
+ let live = true;
2275
+ void editor.save().then((saved) => {
2276
+ if (!live || !saved) return;
2277
+ const bytes = new Uint8Array(saved);
2278
+ setBasedOnTemplate(template.attachedTemplateOf(bytes) === templateFileName(template$1));
2279
+ setLevels(template.linkedLevelStyles(bytes));
2280
+ setDivergence(template.styleDivergence(bytes, template$1.bytes));
2281
+ }).catch(() => live && setDivergence(null));
2282
+ return () => {
2283
+ live = false;
2284
+ };
2285
+ }, [editor, template$1, generation, enabled]);
2286
+ const refresh = React3.useCallback(() => setGeneration((g) => g + 1), []);
2287
+ const insertBlock = React3.useCallback(
2288
+ (name) => {
2289
+ if (!editor || !editable) return false;
2290
+ const fragment = template.buildingBlockFragment(template$1.bytes, name);
2291
+ if (!fragment) return false;
2292
+ try {
2293
+ const result = track({
2294
+ label: `Inserted Quick Part: ${name}`,
2295
+ insert: (revision) => editor.exec({
2296
+ type: "paste",
2297
+ text: "",
2298
+ html: template.fragmentClipboardHtml(
2299
+ revision ? template.trackInsertion(fragment, revision, true) : fragment
2300
+ )
2301
+ }),
2302
+ inserted: (r) => r.ok
2303
+ })?.result;
2304
+ if (!result?.ok) throw new Error(result?.reason ?? "The block could not be inserted here.");
2305
+ refresh();
2306
+ return true;
2307
+ } catch (failure) {
2308
+ notify?.(
2309
+ failure instanceof Error ? failure.message : "Could not insert the block.",
2310
+ "error"
2311
+ );
2312
+ return false;
2313
+ }
2314
+ },
2315
+ [editor, editable, template$1, notify, refresh, track]
2316
+ );
2317
+ const setAsHouseTemplate = React3.useMemo(
2318
+ () => host?.setFromDocument && documentId ? async () => {
2319
+ try {
2320
+ await host.setFromDocument(documentId);
2321
+ setTemplate(await resolveHouseTemplate(host));
2322
+ notify?.("This document is now the house template for new documents.", "success");
2323
+ } catch (failure) {
2324
+ notify?.(
2325
+ failure instanceof Error ? failure.message : "Could not set the house template.",
2326
+ "error"
2327
+ );
2328
+ }
2329
+ } : void 0,
2330
+ [host, documentId, notify]
2331
+ );
2332
+ const value = React3.useMemo(
2333
+ () => ({
2334
+ template: template$1,
2335
+ blocks,
2336
+ divergence,
2337
+ basedOnTemplate,
2338
+ levels,
2339
+ insertBlock,
2340
+ refresh,
2341
+ setAsHouseTemplate,
2342
+ editable
2343
+ }),
2344
+ [
2345
+ template$1,
2346
+ blocks,
2347
+ divergence,
2348
+ basedOnTemplate,
2349
+ levels,
2350
+ insertBlock,
2351
+ refresh,
2352
+ setAsHouseTemplate,
2353
+ editable
2354
+ ]
2355
+ );
2356
+ return /* @__PURE__ */ jsxRuntime.jsx(HouseTemplateContext.Provider, { value: enabled ? value : null, children });
2357
+ }
2150
2358
  function wordDraftHtml(markdown) {
2151
2359
  const template = document.createElement("template");
2152
2360
  template.innerHTML = server.renderToStaticMarkup(/* @__PURE__ */ jsxRuntime.jsx(ui.AiMarkdown, { children: markdown }));
@@ -2239,7 +2447,8 @@ function RibbonMenu({
2239
2447
  icon,
2240
2448
  children,
2241
2449
  className,
2242
- testId
2450
+ testId,
2451
+ title
2243
2452
  }) {
2244
2453
  const [open, setOpen] = React3.useState(false);
2245
2454
  const element = React3.useRef(null);
@@ -2265,7 +2474,7 @@ function RibbonMenu({
2265
2474
  type: "button",
2266
2475
  className: ["native-docx-ribbon-trigger", className].filter(Boolean).join(" "),
2267
2476
  "aria-label": label,
2268
- title: label,
2477
+ title: title ?? label,
2269
2478
  "aria-expanded": open,
2270
2479
  "data-testid": testId,
2271
2480
  onMouseDown: (event) => event.preventDefault(),
@@ -2322,6 +2531,17 @@ function wordShortcut(event) {
2322
2531
  if (event.shiftKey && !event.altKey && event.code === "KeyN") return "Normal";
2323
2532
  return null;
2324
2533
  }
2534
+ function divergenceSummary(divergence) {
2535
+ if (!divergence) return null;
2536
+ const changed = divergence.styles.filter((s) => s.kind === "changed").map((s) => styleLabel(s.name));
2537
+ const missing = divergence.styles.filter((s) => s.kind === "missing").map((s) => styleLabel(s.name));
2538
+ const parts = [
2539
+ divergence.docDefaults ? "default text formatting" : "",
2540
+ changed.length ? `${changed.length === 1 ? "style" : "styles"} ${changed.slice(0, 6).join(", ")}${changed.length > 6 ? "\u2026" : ""}` : "",
2541
+ missing.length ? `${missing.length} house ${missing.length === 1 ? "style" : "styles"} missing` : ""
2542
+ ].filter(Boolean);
2543
+ return parts.length ? `Differs from the house template: ${parts.join("; ")}.` : null;
2544
+ }
2325
2545
  var FILTER_FROM = 8;
2326
2546
  function previewStyle(option) {
2327
2547
  const { fontFamily, fontSizePt, bold, italic, color } = option.preview;
@@ -2337,6 +2557,10 @@ function previewStyle(option) {
2337
2557
  function Gallery({ close }) {
2338
2558
  const { value, setValue, options, isEnabled } = react.useParagraphStyle();
2339
2559
  const editor = react.useDocxEditor();
2560
+ const house = useHouseTemplate();
2561
+ const changed = new Set(
2562
+ house?.basedOnTemplate ? house.divergence?.styles.filter((s) => s.kind === "changed").map((s) => s.styleId) : []
2563
+ );
2340
2564
  const [query, setQuery] = React3.useState("");
2341
2565
  const quick = React3.useMemo(
2342
2566
  () => QUICK_STYLES.map((name) => findStyle(options, name)).filter(
@@ -2399,7 +2623,7 @@ function Gallery({ close }) {
2399
2623
  id: nav.listboxId,
2400
2624
  "aria-label": "Document styles",
2401
2625
  children: [
2402
- shown.map((option, index) => /* @__PURE__ */ jsxRuntime.jsx(
2626
+ shown.map((option, index) => /* @__PURE__ */ jsxRuntime.jsxs(
2403
2627
  "li",
2404
2628
  {
2405
2629
  id: chunk7UTB4PHM_cjs.listOptionId(nav.listboxId, index),
@@ -2410,7 +2634,17 @@ function Gallery({ close }) {
2410
2634
  "data-style-id": option.styleId,
2411
2635
  onMouseEnter: () => nav.setSelectedIndex(index),
2412
2636
  onClick: () => isEnabled && apply(option),
2413
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: previewStyle(option), children: styleLabel(option.name) })
2637
+ children: [
2638
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: previewStyle(option), children: styleLabel(option.name) }),
2639
+ changed.has(option.styleId) && /* @__PURE__ */ jsxRuntime.jsx(
2640
+ "span",
2641
+ {
2642
+ className: "native-docx-style-diverged",
2643
+ title: "Defined differently from the house template",
2644
+ "aria-label": "Defined differently from the house template"
2645
+ }
2646
+ )
2647
+ ]
2414
2648
  },
2415
2649
  option.styleId
2416
2650
  )),
@@ -2424,14 +2658,25 @@ function StyleGallery() {
2424
2658
  const { value, options } = react.useParagraphStyle();
2425
2659
  const current = options.find((o) => o.styleId === value);
2426
2660
  const name = current ? styleLabel(current.name) : "Styles";
2661
+ const house = useHouseTemplate();
2662
+ const diverged = house?.basedOnTemplate ? divergenceSummary(house.divergence) : null;
2427
2663
  return /* @__PURE__ */ jsxRuntime.jsx(
2428
2664
  RibbonMenu,
2429
2665
  {
2430
2666
  label: "Styles",
2667
+ title: diverged ? `Styles. ${diverged}` : void 0,
2431
2668
  className: "native-docx-style-trigger",
2432
2669
  testId: "docx-style-trigger",
2433
2670
  icon: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2434
2671
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "native-docx-style-current", "data-testid": "docx-current-style", children: name }),
2672
+ diverged && /* @__PURE__ */ jsxRuntime.jsx(
2673
+ "span",
2674
+ {
2675
+ className: "native-docx-style-diverged",
2676
+ "data-testid": "docx-style-diverged",
2677
+ "aria-label": diverged
2678
+ }
2679
+ ),
2435
2680
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { size: 12, "aria-hidden": true })
2436
2681
  ] }),
2437
2682
  children: (close) => /* @__PURE__ */ jsxRuntime.jsx(Gallery, { close })
@@ -2500,12 +2745,18 @@ function WordShortcuts({ scope }) {
2500
2745
  const endnote = useInsertNote("endnote");
2501
2746
  const styles = react.useParagraphStyle();
2502
2747
  const editor = react.useDocxEditor();
2503
- const latest = React3.useRef({ footnote, endnote, styles, editor });
2504
- latest.current = { footnote, endnote, styles, editor };
2748
+ const levels = useHouseTemplate()?.levels;
2749
+ const latest = React3.useRef({ footnote, endnote, styles, editor, levels });
2750
+ latest.current = { footnote, endnote, styles, editor, levels };
2505
2751
  React3.useEffect(() => {
2506
2752
  const element = scope.current;
2507
2753
  if (!element) return;
2508
2754
  const onKeyDown = (event) => {
2755
+ if (outlineTab(event)) {
2756
+ event.preventDefault();
2757
+ event.stopPropagation();
2758
+ return;
2759
+ }
2509
2760
  const action = wordShortcut(event);
2510
2761
  if (!action) return;
2511
2762
  const { footnote: footnote2, endnote: endnote2, styles: styles2, editor: editor2 } = latest.current;
@@ -2520,11 +2771,110 @@ function WordShortcuts({ scope }) {
2520
2771
  event.stopPropagation();
2521
2772
  editor2?.surface?.focus();
2522
2773
  };
2774
+ const outlineTab = (event) => {
2775
+ if (event.key !== "Tab" || event.ctrlKey || event.metaKey || event.altKey) return false;
2776
+ const { editor: editor2, levels: levels2, styles: styles2 } = latest.current;
2777
+ const current = editor2?.getSelectionFormatting()?.styleId;
2778
+ const link = current ? levels2?.[current] : void 0;
2779
+ const target = event.shiftKey ? link?.previous : link?.next;
2780
+ if (!target || !editor2 || editorPort(editor2).caret()?.offset !== 0) return false;
2781
+ styles2.setValue(target);
2782
+ return true;
2783
+ };
2523
2784
  element.addEventListener("keydown", onKeyDown, true);
2524
2785
  return () => element.removeEventListener("keydown", onKeyDown, true);
2525
2786
  }, [scope]);
2526
2787
  return null;
2527
2788
  }
2789
+ function QuickParts() {
2790
+ const house = useHouseTemplate();
2791
+ if (!house || house.blocks.length === 0) return null;
2792
+ const categories = [...new Set(house.blocks.map((b) => b.category))];
2793
+ return /* @__PURE__ */ jsxRuntime.jsx(RibbonMenu, { label: "Quick Parts", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Blocks, { size: 16 }), testId: "docx-quick-parts", children: (close) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "native-docx-quick-parts", "data-testid": "docx-quick-parts-panel", children: [
2794
+ categories.map((category) => /* @__PURE__ */ jsxRuntime.jsxs("section", { "aria-label": category, children: [
2795
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { children: category }),
2796
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { children: house.blocks.filter((b) => b.category === category).map((block) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsxs(
2797
+ "button",
2798
+ {
2799
+ type: "button",
2800
+ disabled: !house.editable,
2801
+ title: block.description || block.name,
2802
+ onClick: () => {
2803
+ close();
2804
+ house.insertBlock(block.name);
2805
+ },
2806
+ children: [
2807
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "native-docx-quick-part-name", children: block.name }),
2808
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "native-docx-quick-part-preview", children: block.text })
2809
+ ]
2810
+ }
2811
+ ) }, block.name)) })
2812
+ ] }, category)),
2813
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "native-docx-quick-parts-source", children: [
2814
+ "From ",
2815
+ house.template.name
2816
+ ] })
2817
+ ] }) });
2818
+ }
2819
+ var SOURCE = {
2820
+ workspace: "This workspace's house template",
2821
+ deployment: "The house template for all workspaces",
2822
+ builtin: "ARBI\u2019s built-in template (no house template has been set)"
2823
+ };
2824
+ function download(bytes, name) {
2825
+ const isTemplate = !/\.docx$/i.test(name);
2826
+ const url = URL.createObjectURL(
2827
+ new Blob([bytes], {
2828
+ type: isTemplate ? "application/vnd.openxmlformats-officedocument.wordprocessingml.template" : "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
2829
+ })
2830
+ );
2831
+ const a = document.createElement("a");
2832
+ a.href = url;
2833
+ a.download = /\.(dotx|docx)$/i.test(name) ? name : `${name}.dotx`;
2834
+ a.click();
2835
+ setTimeout(() => URL.revokeObjectURL(url), 5e3);
2836
+ }
2837
+ function HouseTemplatePanel() {
2838
+ const house = useHouseTemplate();
2839
+ if (!house) return null;
2840
+ const summary = divergenceSummary(house.divergence);
2841
+ return /* @__PURE__ */ jsxRuntime.jsx(
2842
+ RibbonMenu,
2843
+ {
2844
+ label: "House template",
2845
+ className: "native-docx-style-trigger",
2846
+ testId: "docx-house-template",
2847
+ icon: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2848
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "native-docx-style-current", children: "House template" }),
2849
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { size: 12, "aria-hidden": true })
2850
+ ] }),
2851
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "native-docx-house-template", "data-testid": "docx-house-template-panel", children: [
2852
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
2853
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: house.template.name }),
2854
+ /* @__PURE__ */ jsxRuntime.jsx("br", {}),
2855
+ SOURCE[house.template.source],
2856
+ ". New documents start from it."
2857
+ ] }),
2858
+ /* @__PURE__ */ jsxRuntime.jsx("p", { "data-testid": "docx-house-template-status", children: house.divergence === null ? "Comparing this document\u2019s styles\u2026" : !house.basedOnTemplate ? "This document was not started from the house template." : summary ?? "This document\u2019s styles match the house template." }),
2859
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "native-docx-house-template-actions", children: [
2860
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", onClick: () => download(house.template.bytes, house.template.name), children: [
2861
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { size: 14 }),
2862
+ " Download template"
2863
+ ] }),
2864
+ house.setAsHouseTemplate && /* @__PURE__ */ jsxRuntime.jsx(
2865
+ "button",
2866
+ {
2867
+ type: "button",
2868
+ "data-testid": "docx-set-house-template",
2869
+ onClick: () => void house.setAsHouseTemplate(),
2870
+ children: "Use this document as the house template"
2871
+ }
2872
+ )
2873
+ ] })
2874
+ ] })
2875
+ }
2876
+ );
2877
+ }
2528
2878
  function contentPath(documentId) {
2529
2879
  const match = /^(doc-[A-Za-z0-9]+)@v([1-9][0-9]*)$/.exec(documentId);
2530
2880
  return match ? `/${encodeURIComponent(match[1])}/content?version=${match[2]}` : `/${encodeURIComponent(documentId)}/content`;
@@ -2614,6 +2964,7 @@ function DocxRibbon({
2614
2964
  /* @__PURE__ */ jsxRuntime.jsxs("select", { "aria-label": "Ribbon tab", value: tab, onChange: (event) => setTab(event.target.value), children: [
2615
2965
  /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Home" }),
2616
2966
  /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Insert" }),
2967
+ /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Design" }),
2617
2968
  /* @__PURE__ */ jsxRuntime.jsx("option", { children: "References" }),
2618
2969
  /* @__PURE__ */ jsxRuntime.jsx("option", { children: "Review" })
2619
2970
  ] }),
@@ -2647,8 +2998,10 @@ function DocxRibbon({
2647
2998
  /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.Toolbar.TableInsert, {}),
2648
2999
  /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.Toolbar.ImageInsert, {}),
2649
3000
  /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.Toolbar.Link, {}),
2650
- /* @__PURE__ */ jsxRuntime.jsx(DefinedTermButtons, {})
3001
+ /* @__PURE__ */ jsxRuntime.jsx(DefinedTermButtons, {}),
3002
+ /* @__PURE__ */ jsxRuntime.jsx(QuickParts, {})
2651
3003
  ] }),
3004
+ tab === "Design" && /* @__PURE__ */ jsxRuntime.jsx(HouseTemplatePanel, {}),
2652
3005
  tab === "References" && /* @__PURE__ */ jsxRuntime.jsx(NoteButtons, {}),
2653
3006
  tab === "Review" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2654
3007
  /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.Toolbar.EditingMode, {}),
@@ -2723,11 +3076,16 @@ function NativeDocxEditor({
2723
3076
  async function start() {
2724
3077
  const fonts = await adapter.loadFonts();
2725
3078
  if (isDraft || snapshot) {
2726
- const data = isDraft ? !streamRef.current && drafts.get(documentId) || await (await adapter.request("/prepare", {
3079
+ const data = isDraft ? !streamRef.current && drafts.get(documentId) || (draftContent === "" && adapter.houseTemplate ? await (async () => {
3080
+ const template$1 = await resolveHouseTemplate(adapter.houseTemplate);
3081
+ return template.newFromTemplate(template$1.bytes, {
3082
+ attachedTemplate: templateFileName(template$1)
3083
+ });
3084
+ })() : await (await adapter.request("/prepare", {
2727
3085
  method: "POST",
2728
3086
  headers: { "Content-Type": "application/json" },
2729
3087
  body: JSON.stringify({ content: draftContent })
2730
- })).arrayBuffer() : await (await adapter.request(contentPath(documentId))).arrayBuffer();
3088
+ })).arrayBuffer()) : await (await adapter.request(contentPath(documentId))).arrayBuffer();
2731
3089
  if (!cancelled) {
2732
3090
  setReady({
2733
3091
  document: new Uint8Array(data),
@@ -2989,70 +3347,80 @@ function NativeDocxEditor({
2989
3347
  if (change.source !== "load") changed();
2990
3348
  },
2991
3349
  onFontError: (e) => setError(e.message),
2992
- children: /* @__PURE__ */ jsxRuntime.jsxs(
3350
+ children: /* @__PURE__ */ jsxRuntime.jsx(DocxAuthorContext.Provider, { value: ready.author, children: /* @__PURE__ */ jsxRuntime.jsx(
2993
3351
  DefinedTermsProvider,
2994
3352
  {
2995
3353
  load: adapter.definitions,
2996
3354
  editable: !stream && !snapshot && ready.canEdit,
2997
3355
  notify: adapter.notify,
2998
- children: [
2999
- /* @__PURE__ */ jsxRuntime.jsx(
3000
- react.DocxEditor.RevisionMarkup,
3001
- {
3002
- insertions: { mark: "underline", color: "blue" },
3003
- deletions: { mark: "strikethrough", color: "red" },
3004
- movedFrom: { mark: "doubleStrikethrough", color: "green" },
3005
- movedTo: { mark: "doubleUnderline", color: "green" },
3006
- trackMoves: true
3007
- }
3008
- ),
3009
- !stream && /* @__PURE__ */ jsxRuntime.jsx(
3010
- DocxRibbon,
3011
- {
3012
- onSave: () => void saveVersion(),
3013
- badge: (
3014
- // Says the document is shared live, in place of a title row above the ribbon.
3015
- ready.room && ready.canEdit && !snapshot ? /* @__PURE__ */ jsxRuntime.jsx("span", { title: "Edits sync live with everyone who has this document open", children: "Co-edit mode" }) : void 0
3356
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
3357
+ HouseTemplateProvider,
3358
+ {
3359
+ enabled: !stream,
3360
+ host: adapter.houseTemplate,
3361
+ documentId: isDraft || snapshot ? void 0 : documentId,
3362
+ editable: !stream && !snapshot && ready.canEdit,
3363
+ notify: adapter.notify,
3364
+ children: [
3365
+ /* @__PURE__ */ jsxRuntime.jsx(
3366
+ react.DocxEditor.RevisionMarkup,
3367
+ {
3368
+ insertions: { mark: "underline", color: "blue" },
3369
+ deletions: { mark: "strikethrough", color: "red" },
3370
+ movedFrom: { mark: "doubleStrikethrough", color: "green" },
3371
+ movedTo: { mark: "doubleUnderline", color: "green" },
3372
+ trackMoves: true
3373
+ }
3374
+ ),
3375
+ !stream && /* @__PURE__ */ jsxRuntime.jsx(
3376
+ DocxRibbon,
3377
+ {
3378
+ onSave: () => void saveVersion(),
3379
+ badge: (
3380
+ // Says the document is shared live, in place of a title row above the ribbon.
3381
+ ready.room && ready.canEdit && !snapshot ? /* @__PURE__ */ jsxRuntime.jsx("span", { title: "Edits sync live with everyone who has this document open", children: "Co-edit mode" }) : void 0
3382
+ )
3383
+ }
3384
+ ),
3385
+ !stream && /* @__PURE__ */ jsxRuntime.jsx(WordShortcuts, { scope: stageRef }),
3386
+ /* @__PURE__ */ jsxRuntime.jsxs(
3387
+ "div",
3388
+ {
3389
+ className: "native-docx-stage",
3390
+ ref: stageRef,
3391
+ onMouseDownCapture: (event) => {
3392
+ if (event.button === 2 && ref.current?.getSelectionPlacement())
3393
+ event.preventDefault();
3394
+ },
3395
+ children: [
3396
+ /* @__PURE__ */ jsxRuntime.jsxs(react.DocxEditor.Viewport, { className: "native-docx-viewport", children: [
3397
+ /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.Content, {}),
3398
+ /* @__PURE__ */ jsxRuntime.jsx(
3399
+ DocxMentions,
3400
+ {
3401
+ initialCommentId,
3402
+ mentions: adapter.mentions,
3403
+ notify: adapter.notify,
3404
+ getAgentStatus: adapter.getAgentStatus,
3405
+ requestAgent: adapter.requestAgent ? (request) => adapter.requestAgent(documentId, request) : void 0,
3406
+ children: /* @__PURE__ */ jsxRuntime.jsx(DocxEditorReview, { children: () => /* @__PURE__ */ jsxRuntime.jsx(DocxEditorReview.Card, { children: /* @__PURE__ */ jsxRuntime.jsx(DocxCommentActivity, {}) }) })
3407
+ }
3408
+ ),
3409
+ /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.HeaderFooterChrome, {}),
3410
+ /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.NotesChrome, {}),
3411
+ /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.Equation, {}),
3412
+ /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.HyperLink, {}),
3413
+ /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.ContextMenu, {})
3414
+ ] }),
3415
+ /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.PageNumber, {})
3416
+ ]
3417
+ }
3016
3418
  )
3017
- }
3018
- ),
3019
- !stream && /* @__PURE__ */ jsxRuntime.jsx(WordShortcuts, { scope: stageRef }),
3020
- /* @__PURE__ */ jsxRuntime.jsxs(
3021
- "div",
3022
- {
3023
- className: "native-docx-stage",
3024
- ref: stageRef,
3025
- onMouseDownCapture: (event) => {
3026
- if (event.button === 2 && ref.current?.getSelectionPlacement())
3027
- event.preventDefault();
3028
- },
3029
- children: [
3030
- /* @__PURE__ */ jsxRuntime.jsxs(react.DocxEditor.Viewport, { className: "native-docx-viewport", children: [
3031
- /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.Content, {}),
3032
- /* @__PURE__ */ jsxRuntime.jsx(
3033
- DocxMentions,
3034
- {
3035
- initialCommentId,
3036
- mentions: adapter.mentions,
3037
- notify: adapter.notify,
3038
- getAgentStatus: adapter.getAgentStatus,
3039
- requestAgent: adapter.requestAgent ? (request) => adapter.requestAgent(documentId, request) : void 0,
3040
- children: /* @__PURE__ */ jsxRuntime.jsx(DocxEditorReview, { children: () => /* @__PURE__ */ jsxRuntime.jsx(DocxEditorReview.Card, { children: /* @__PURE__ */ jsxRuntime.jsx(DocxCommentActivity, {}) }) })
3041
- }
3042
- ),
3043
- /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.HeaderFooterChrome, {}),
3044
- /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.NotesChrome, {}),
3045
- /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.Equation, {}),
3046
- /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.HyperLink, {}),
3047
- /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.ContextMenu, {})
3048
- ] }),
3049
- /* @__PURE__ */ jsxRuntime.jsx(react.DocxEditor.PageNumber, {})
3050
- ]
3051
- }
3052
- )
3053
- ]
3419
+ ]
3420
+ }
3421
+ )
3054
3422
  }
3055
- )
3423
+ ) })
3056
3424
  }
3057
3425
  ) }, retry) }),
3058
3426
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "native-docx-actions", children: [
@@ -3147,25 +3515,36 @@ async function loadFonts(baseUrl) {
3147
3515
  };
3148
3516
  }
3149
3517
 
3518
+ exports.BUILTIN_TEMPLATE_NAME = BUILTIN_TEMPLATE_NAME;
3150
3519
  exports.DEFAULT_DEFINED_TERMS_LABELS = DEFAULT_DEFINED_TERMS_LABELS;
3151
3520
  exports.DEFINED_TERM_TRIGGER = DEFINED_TERM_TRIGGER;
3152
3521
  exports.DefinedTermsProvider = DefinedTermsProvider;
3522
+ exports.DocxAuthorContext = DocxAuthorContext;
3153
3523
  exports.DocxCommentActivity = DocxCommentActivity;
3154
3524
  exports.DocxEditorReview = DocxEditorReview;
3155
3525
  exports.DocxMentions = DocxMentions;
3156
3526
  exports.DocxParticipants = DocxParticipants;
3157
3527
  exports.DocxRibbon = DocxRibbon;
3528
+ exports.HouseTemplateProvider = HouseTemplateProvider;
3158
3529
  exports.NativeDocxEditor = NativeDocxEditor;
3530
+ exports.builtinHouseTemplate = builtinHouseTemplate;
3531
+ exports.contentPath = contentPath;
3159
3532
  exports.editorPort = editorPort;
3160
3533
  exports.findDefinitionsSection = findDefinitionsSection;
3161
3534
  exports.insertDefinedTerm = insertDefinedTerm;
3162
3535
  exports.loadDocxFonts = loadDocxFonts;
3163
3536
  exports.normalizeDocxExport = normalizeDocxExport;
3537
+ exports.resolveHouseTemplate = resolveHouseTemplate;
3164
3538
  exports.reviewModule = reviewModule;
3539
+ exports.structuralPort = structuralPort;
3540
+ exports.templateFileName = templateFileName;
3541
+ exports.trackedOrNoted = trackedOrNoted;
3165
3542
  exports.updateDefinitions = updateDefinitions;
3166
3543
  exports.useDefinedTerms = useDefinedTerms;
3544
+ exports.useHouseTemplate = useHouseTemplate;
3167
3545
  exports.useReview = useReview;
3168
3546
  exports.useReviewItem = useReviewItem;
3547
+ exports.useTrackedOrNoted = useTrackedOrNoted;
3169
3548
  exports.withDirectEditing = withDirectEditing;
3170
- //# sourceMappingURL=chunk-HGJGALLN.cjs.map
3171
- //# sourceMappingURL=chunk-HGJGALLN.cjs.map
3549
+ //# sourceMappingURL=chunk-44TPPPRJ.cjs.map
3550
+ //# sourceMappingURL=chunk-44TPPPRJ.cjs.map