@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
@@ -6,9 +6,10 @@ import { collectReviewItems, revisionItemsOf } from '@docx-editor.dev/core/store
6
6
  import React3, { createContext, useContext, useState, useRef, useCallback, useEffect, useLayoutEffect, useMemo } from 'react';
7
7
  import { useDocxEditor, ReviewRailContext, useEditorSnapshot, useChromeTranslate, DocxEditor, createFontSource, useParagraphStyle, useEditorCommand } from '@docx-editor.dev/react';
8
8
  import { AiMarkdown, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Button, DialogFooter, cn, splitMarkdownBlocks } from '@arbidocs/react/ui';
9
- import { MessageSquarePlus, MessageSquare, PencilLine, ChevronLeft, ChevronRight, X, ChevronUp, ChevronDown, CheckCheck, RotateCcw, CircleCheck, Trash2, Check, SendHorizontal, Type, Ellipsis, RefreshCw, WifiOff, Download, Save, BookA, ListRestart, Superscript, NotebookText } from 'lucide-react';
9
+ import { MessageSquarePlus, MessageSquare, PencilLine, ChevronLeft, ChevronRight, X, ChevronUp, ChevronDown, CheckCheck, RotateCcw, CircleCheck, Trash2, Check, SendHorizontal, Type, Ellipsis, RefreshCw, WifiOff, Download, Save, BookA, ListRestart, Blocks, Superscript, NotebookText } from 'lucide-react';
10
10
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
11
11
  import { createPortal } from 'react-dom';
12
+ import { defaultLegalTemplate, listBuildingBlocks, attachedTemplateOf, linkedLevelStyles, styleDivergence, buildingBlockFragment, fragmentClipboardHtml, trackInsertion, newFromTemplate } from '@arbidocs/coedit/template';
12
13
  import { renderToStaticMarkup } from 'react-dom/server';
13
14
  import * as Y from 'yjs';
14
15
  import { DOCUMENT_COLLABORATION_VERSIONS, collaborationModule } from '@arbidocs/coedit';
@@ -1259,8 +1260,6 @@ function DocxMentions({
1259
1260
  }
1260
1261
  ) });
1261
1262
  }
1262
-
1263
- // src/docx/definitions/fragment.ts
1264
1263
  var DEFINED_TERM_STYLE = "DefinedTerm";
1265
1264
  var FRAGMENT_STYLES = stylesXml(
1266
1265
  "",
@@ -1293,12 +1292,13 @@ function base64(bytes) {
1293
1292
  binary += String.fromCharCode(...bytes.subarray(i, i + 32768));
1294
1293
  return btoa(binary);
1295
1294
  }
1296
- function fragmentHtml(body, covered2) {
1297
- const bytes = docxPackage({
1295
+ function fragmentHtml(body, covered2, revision) {
1296
+ const untracked = docxPackage({
1298
1297
  document: documentXml(body),
1299
1298
  styles: FRAGMENT_STYLES,
1300
1299
  title: "Defined terms"
1301
1300
  });
1301
+ const bytes = revision ? trackInsertion(untracked, revision, covered2) : untracked;
1302
1302
  return `<div data-docx-fragment="${base64(bytes)}" data-docx-fragment-end="${covered2 ? "covered" : "open"}"></div>`;
1303
1303
  }
1304
1304
 
@@ -1555,7 +1555,7 @@ function readParagraph(node, topLevel, bookmarks) {
1555
1555
  links
1556
1556
  };
1557
1557
  }
1558
- function editorPort(editor) {
1558
+ function editorPort(editor, revision) {
1559
1559
  const surface = () => {
1560
1560
  const s = editor.surface;
1561
1561
  if (!s) throw new Error("The document is not open.");
@@ -1574,7 +1574,7 @@ function editorPort(editor) {
1574
1574
  surface().setSelection({ anchor: from, head: to });
1575
1575
  surface().deleteSelection();
1576
1576
  },
1577
- paste: (body, text, covered2) => editor.exec({ type: "paste", text, html: fragmentHtml(body, covered2) }).ok,
1577
+ paste: (body, text, covered2) => editor.exec({ type: "paste", text, html: fragmentHtml(body, covered2, revision) }).ok,
1578
1578
  splitParagraph: () => surface().splitParagraph()
1579
1579
  };
1580
1580
  }
@@ -1617,6 +1617,70 @@ function holdCaret(editor, timeoutMs = 5e3) {
1617
1617
  const offSelection = editor.on("selectionChange", restore);
1618
1618
  const timer = setTimeout(stop, timeoutMs);
1619
1619
  }
1620
+
1621
+ // src/docx/structural/trackedOrNoted.ts
1622
+ var revisionDate = (at = /* @__PURE__ */ new Date()) => at.toISOString().replace(/\.\d{3}Z$/, "Z");
1623
+ function trackedOrNoted(port, { label, author, insert, inserted = Boolean }) {
1624
+ const mode = port.mode();
1625
+ if (mode === "viewing") throw new Error("This document is read only.");
1626
+ if (mode !== "suggesting") return { result: insert(null), how: "direct" };
1627
+ const revision = { author, date: revisionDate() };
1628
+ const before = port.caret();
1629
+ const tracked = port.trackedBy(revision);
1630
+ port.setMode("editing");
1631
+ let result;
1632
+ try {
1633
+ result = insert(revision);
1634
+ } finally {
1635
+ port.setMode("suggesting");
1636
+ }
1637
+ if (!inserted(result) || port.trackedBy(revision) > tracked)
1638
+ return { result, how: inserted(result) ? "tracked" : "direct" };
1639
+ const after = port.caret();
1640
+ if (before && after && port.comment(before, after, label)) return { result, how: "noted" };
1641
+ return { result, how: "direct" };
1642
+ }
1643
+ var attr2 = (node, name) => node.attributes?.find((a) => a.localName === name)?.value;
1644
+ function structuralPort(editor) {
1645
+ return {
1646
+ mode: () => editor.getEditingMode(),
1647
+ setMode: (mode) => void editor.setEditingMode(mode),
1648
+ caret: () => {
1649
+ const head = editor.surface?.state().selection?.head;
1650
+ return head ? { paragraphId: head.paragraphId, offset: head.offset } : null;
1651
+ },
1652
+ // Read from the story's tree: the review pane merges adjacent insertions by one author into
1653
+ // one card, so a card's date need not be this insert's.
1654
+ trackedBy: ({ author, date }) => {
1655
+ const root = editor.surface?.session.part().root;
1656
+ let count = 0;
1657
+ const visit = (node) => {
1658
+ if (node.localName === "ins" && attr2(node, "author") === author && attr2(node, "date") === date)
1659
+ count++;
1660
+ node.children?.forEach(visit);
1661
+ };
1662
+ if (root) visit(root);
1663
+ return count;
1664
+ },
1665
+ comment: (from, to, text) => {
1666
+ const surface = editor.surface;
1667
+ if (!surface) return false;
1668
+ surface.setSelection({ anchor: from, head: to });
1669
+ const ok2 = editor.addComment(text).ok;
1670
+ surface.setSelection({ anchor: to, head: to });
1671
+ return ok2;
1672
+ }
1673
+ };
1674
+ }
1675
+ var DocxAuthorContext = createContext("You");
1676
+ function useTrackedOrNoted() {
1677
+ const editor = useDocxEditor();
1678
+ const author = useContext(DocxAuthorContext);
1679
+ return useCallback(
1680
+ (spec) => editor ? trackedOrNoted(structuralPort(editor), { ...spec, author }) : void 0,
1681
+ [editor, author]
1682
+ );
1683
+ }
1620
1684
  var DEFINED_TERM_TRIGGER = "@@";
1621
1685
  var TRIGGER_QUERY = /(?:^|[\s([{"“‘'])@@([^@\n\t]{0,40})$/;
1622
1686
  var MAX_GROUPS = 8;
@@ -1660,6 +1724,7 @@ function DefinedTermsProvider({
1660
1724
  labels
1661
1725
  }) {
1662
1726
  const editor = useDocxEditor();
1727
+ const track = useTrackedOrNoted();
1663
1728
  const l = useMemo(() => ({ ...DEFAULT_DEFINED_TERMS_LABELS, ...labels }), [labels]);
1664
1729
  const [sources, setSources] = useState(null);
1665
1730
  const loadedAt = useRef(0);
@@ -1716,35 +1781,46 @@ function DefinedTermsProvider({
1716
1781
  refreshRef.current = refresh;
1717
1782
  const firstHover = useCallback(() => void refreshRef.current(), []);
1718
1783
  const caretRect = () => container().querySelector(".docx-editor-one-surface__caret")?.getBoundingClientRect();
1719
- const run = useCallback(
1784
+ const attempt = useCallback(
1720
1785
  (fn) => {
1721
- if (!editor) return void 0;
1722
1786
  try {
1723
- return withDirectEditing(editor, fn);
1787
+ return fn();
1724
1788
  } catch (error) {
1725
1789
  notify?.(error instanceof Error ? error.message : "Could not change the document.", "error");
1726
1790
  return void 0;
1727
1791
  }
1728
1792
  },
1729
- [editor, notify]
1793
+ [notify]
1794
+ );
1795
+ const run = useCallback(
1796
+ (fn) => editor ? attempt(() => withDirectEditing(editor, fn)) : void 0,
1797
+ [editor, attempt]
1730
1798
  );
1731
1799
  const pick = useCallback(
1732
1800
  (row) => {
1733
1801
  if (!editor || !picker || !row) return;
1734
1802
  const port = editorPort(editor);
1735
- const result = run(() => {
1803
+ const placed = run(() => {
1736
1804
  if (picker.mode === "search" && picker.caret) port.setCaret(picker.caret);
1737
- return insertDefinedTerm(port, row.definition, {
1738
- replace: picker.mode === "typing" ? DEFINED_TERM_TRIGGER.length + picker.query.length : 0
1739
- });
1805
+ const caret = port.caret();
1806
+ if (!caret) throw new Error("Place the cursor where the term should go.");
1807
+ const typed = picker.mode === "typing" ? DEFINED_TERM_TRIGGER.length + picker.query.length : 0;
1808
+ if (typed) port.deleteRange({ ...caret, offset: Math.max(0, caret.offset - typed) }, caret);
1809
+ return true;
1740
1810
  });
1811
+ const result = placed ? attempt(
1812
+ () => track({
1813
+ label: `Inserted defined term: ${row.definition.term}`,
1814
+ insert: (revision) => insertDefinedTerm(editorPort(editor, revision), row.definition)
1815
+ })?.result
1816
+ ) : void 0;
1741
1817
  setPicker(null);
1742
1818
  editor.surface?.focus();
1743
1819
  if (result) holdCaret(editor);
1744
1820
  if (result?.addedDefinition)
1745
1821
  notify?.(`\u201C${row.definition.term}\u201D added to the Definitions section.`, "success");
1746
1822
  },
1747
- [editor, picker, run, notify]
1823
+ [editor, picker, run, attempt, track, notify]
1748
1824
  );
1749
1825
  const openPicker = useCallback(
1750
1826
  (anchor) => {
@@ -2122,6 +2198,138 @@ function DefinedTermHover({
2122
2198
  document.body
2123
2199
  );
2124
2200
  }
2201
+ var BUILTIN_TEMPLATE_NAME = "ARBI legal template";
2202
+ var templateFileName = (t) => /\.(dotx|dotm|docx|docm)$/i.test(t.name) ? t.name : `${t.name}.dotx`;
2203
+ var builtin;
2204
+ function builtinHouseTemplate() {
2205
+ builtin ??= { bytes: defaultLegalTemplate(), name: BUILTIN_TEMPLATE_NAME, source: "builtin" };
2206
+ return builtin;
2207
+ }
2208
+ async function resolveHouseTemplate(host) {
2209
+ if (!host) return builtinHouseTemplate();
2210
+ try {
2211
+ return await host.load();
2212
+ } catch {
2213
+ return builtinHouseTemplate();
2214
+ }
2215
+ }
2216
+ var HouseTemplateContext = createContext(null);
2217
+ var useHouseTemplate = () => useContext(HouseTemplateContext);
2218
+ function HouseTemplateProvider({
2219
+ host,
2220
+ documentId,
2221
+ editable,
2222
+ notify,
2223
+ enabled = true,
2224
+ children
2225
+ }) {
2226
+ const editor = useDocxEditor();
2227
+ const track = useTrackedOrNoted();
2228
+ const [template, setTemplate] = useState(builtinHouseTemplate);
2229
+ const [divergence, setDivergence] = useState(null);
2230
+ const [basedOnTemplate, setBasedOnTemplate] = useState(false);
2231
+ const [levels, setLevels] = useState({});
2232
+ const [generation, setGeneration] = useState(0);
2233
+ useEffect(() => {
2234
+ let live = true;
2235
+ void resolveHouseTemplate(host).then((t) => live && setTemplate(t));
2236
+ return () => {
2237
+ live = false;
2238
+ };
2239
+ }, [host]);
2240
+ const blocks = useMemo(() => {
2241
+ try {
2242
+ return listBuildingBlocks(template.bytes);
2243
+ } catch {
2244
+ return [];
2245
+ }
2246
+ }, [template]);
2247
+ useEffect(() => {
2248
+ if (!editor || !enabled) return;
2249
+ let live = true;
2250
+ void editor.save().then((saved) => {
2251
+ if (!live || !saved) return;
2252
+ const bytes = new Uint8Array(saved);
2253
+ setBasedOnTemplate(attachedTemplateOf(bytes) === templateFileName(template));
2254
+ setLevels(linkedLevelStyles(bytes));
2255
+ setDivergence(styleDivergence(bytes, template.bytes));
2256
+ }).catch(() => live && setDivergence(null));
2257
+ return () => {
2258
+ live = false;
2259
+ };
2260
+ }, [editor, template, generation, enabled]);
2261
+ const refresh = useCallback(() => setGeneration((g) => g + 1), []);
2262
+ const insertBlock = useCallback(
2263
+ (name) => {
2264
+ if (!editor || !editable) return false;
2265
+ const fragment = buildingBlockFragment(template.bytes, name);
2266
+ if (!fragment) return false;
2267
+ try {
2268
+ const result = track({
2269
+ label: `Inserted Quick Part: ${name}`,
2270
+ insert: (revision) => editor.exec({
2271
+ type: "paste",
2272
+ text: "",
2273
+ html: fragmentClipboardHtml(
2274
+ revision ? trackInsertion(fragment, revision, true) : fragment
2275
+ )
2276
+ }),
2277
+ inserted: (r) => r.ok
2278
+ })?.result;
2279
+ if (!result?.ok) throw new Error(result?.reason ?? "The block could not be inserted here.");
2280
+ refresh();
2281
+ return true;
2282
+ } catch (failure) {
2283
+ notify?.(
2284
+ failure instanceof Error ? failure.message : "Could not insert the block.",
2285
+ "error"
2286
+ );
2287
+ return false;
2288
+ }
2289
+ },
2290
+ [editor, editable, template, notify, refresh, track]
2291
+ );
2292
+ const setAsHouseTemplate = useMemo(
2293
+ () => host?.setFromDocument && documentId ? async () => {
2294
+ try {
2295
+ await host.setFromDocument(documentId);
2296
+ setTemplate(await resolveHouseTemplate(host));
2297
+ notify?.("This document is now the house template for new documents.", "success");
2298
+ } catch (failure) {
2299
+ notify?.(
2300
+ failure instanceof Error ? failure.message : "Could not set the house template.",
2301
+ "error"
2302
+ );
2303
+ }
2304
+ } : void 0,
2305
+ [host, documentId, notify]
2306
+ );
2307
+ const value = useMemo(
2308
+ () => ({
2309
+ template,
2310
+ blocks,
2311
+ divergence,
2312
+ basedOnTemplate,
2313
+ levels,
2314
+ insertBlock,
2315
+ refresh,
2316
+ setAsHouseTemplate,
2317
+ editable
2318
+ }),
2319
+ [
2320
+ template,
2321
+ blocks,
2322
+ divergence,
2323
+ basedOnTemplate,
2324
+ levels,
2325
+ insertBlock,
2326
+ refresh,
2327
+ setAsHouseTemplate,
2328
+ editable
2329
+ ]
2330
+ );
2331
+ return /* @__PURE__ */ jsx(HouseTemplateContext.Provider, { value: enabled ? value : null, children });
2332
+ }
2125
2333
  function wordDraftHtml(markdown) {
2126
2334
  const template = document.createElement("template");
2127
2335
  template.innerHTML = renderToStaticMarkup(/* @__PURE__ */ jsx(AiMarkdown, { children: markdown }));
@@ -2214,7 +2422,8 @@ function RibbonMenu({
2214
2422
  icon,
2215
2423
  children,
2216
2424
  className,
2217
- testId
2425
+ testId,
2426
+ title
2218
2427
  }) {
2219
2428
  const [open, setOpen] = useState(false);
2220
2429
  const element = useRef(null);
@@ -2240,7 +2449,7 @@ function RibbonMenu({
2240
2449
  type: "button",
2241
2450
  className: ["native-docx-ribbon-trigger", className].filter(Boolean).join(" "),
2242
2451
  "aria-label": label,
2243
- title: label,
2452
+ title: title ?? label,
2244
2453
  "aria-expanded": open,
2245
2454
  "data-testid": testId,
2246
2455
  onMouseDown: (event) => event.preventDefault(),
@@ -2297,6 +2506,17 @@ function wordShortcut(event) {
2297
2506
  if (event.shiftKey && !event.altKey && event.code === "KeyN") return "Normal";
2298
2507
  return null;
2299
2508
  }
2509
+ function divergenceSummary(divergence) {
2510
+ if (!divergence) return null;
2511
+ const changed = divergence.styles.filter((s) => s.kind === "changed").map((s) => styleLabel(s.name));
2512
+ const missing = divergence.styles.filter((s) => s.kind === "missing").map((s) => styleLabel(s.name));
2513
+ const parts = [
2514
+ divergence.docDefaults ? "default text formatting" : "",
2515
+ changed.length ? `${changed.length === 1 ? "style" : "styles"} ${changed.slice(0, 6).join(", ")}${changed.length > 6 ? "\u2026" : ""}` : "",
2516
+ missing.length ? `${missing.length} house ${missing.length === 1 ? "style" : "styles"} missing` : ""
2517
+ ].filter(Boolean);
2518
+ return parts.length ? `Differs from the house template: ${parts.join("; ")}.` : null;
2519
+ }
2300
2520
  var FILTER_FROM = 8;
2301
2521
  function previewStyle(option) {
2302
2522
  const { fontFamily, fontSizePt, bold, italic, color } = option.preview;
@@ -2312,6 +2532,10 @@ function previewStyle(option) {
2312
2532
  function Gallery({ close }) {
2313
2533
  const { value, setValue, options, isEnabled } = useParagraphStyle();
2314
2534
  const editor = useDocxEditor();
2535
+ const house = useHouseTemplate();
2536
+ const changed = new Set(
2537
+ house?.basedOnTemplate ? house.divergence?.styles.filter((s) => s.kind === "changed").map((s) => s.styleId) : []
2538
+ );
2315
2539
  const [query, setQuery] = useState("");
2316
2540
  const quick = useMemo(
2317
2541
  () => QUICK_STYLES.map((name) => findStyle(options, name)).filter(
@@ -2374,7 +2598,7 @@ function Gallery({ close }) {
2374
2598
  id: nav.listboxId,
2375
2599
  "aria-label": "Document styles",
2376
2600
  children: [
2377
- shown.map((option, index) => /* @__PURE__ */ jsx(
2601
+ shown.map((option, index) => /* @__PURE__ */ jsxs(
2378
2602
  "li",
2379
2603
  {
2380
2604
  id: listOptionId(nav.listboxId, index),
@@ -2385,7 +2609,17 @@ function Gallery({ close }) {
2385
2609
  "data-style-id": option.styleId,
2386
2610
  onMouseEnter: () => nav.setSelectedIndex(index),
2387
2611
  onClick: () => isEnabled && apply(option),
2388
- children: /* @__PURE__ */ jsx("span", { style: previewStyle(option), children: styleLabel(option.name) })
2612
+ children: [
2613
+ /* @__PURE__ */ jsx("span", { style: previewStyle(option), children: styleLabel(option.name) }),
2614
+ changed.has(option.styleId) && /* @__PURE__ */ jsx(
2615
+ "span",
2616
+ {
2617
+ className: "native-docx-style-diverged",
2618
+ title: "Defined differently from the house template",
2619
+ "aria-label": "Defined differently from the house template"
2620
+ }
2621
+ )
2622
+ ]
2389
2623
  },
2390
2624
  option.styleId
2391
2625
  )),
@@ -2399,14 +2633,25 @@ function StyleGallery() {
2399
2633
  const { value, options } = useParagraphStyle();
2400
2634
  const current = options.find((o) => o.styleId === value);
2401
2635
  const name = current ? styleLabel(current.name) : "Styles";
2636
+ const house = useHouseTemplate();
2637
+ const diverged = house?.basedOnTemplate ? divergenceSummary(house.divergence) : null;
2402
2638
  return /* @__PURE__ */ jsx(
2403
2639
  RibbonMenu,
2404
2640
  {
2405
2641
  label: "Styles",
2642
+ title: diverged ? `Styles. ${diverged}` : void 0,
2406
2643
  className: "native-docx-style-trigger",
2407
2644
  testId: "docx-style-trigger",
2408
2645
  icon: /* @__PURE__ */ jsxs(Fragment, { children: [
2409
2646
  /* @__PURE__ */ jsx("span", { className: "native-docx-style-current", "data-testid": "docx-current-style", children: name }),
2647
+ diverged && /* @__PURE__ */ jsx(
2648
+ "span",
2649
+ {
2650
+ className: "native-docx-style-diverged",
2651
+ "data-testid": "docx-style-diverged",
2652
+ "aria-label": diverged
2653
+ }
2654
+ ),
2410
2655
  /* @__PURE__ */ jsx(ChevronDown, { size: 12, "aria-hidden": true })
2411
2656
  ] }),
2412
2657
  children: (close) => /* @__PURE__ */ jsx(Gallery, { close })
@@ -2475,12 +2720,18 @@ function WordShortcuts({ scope }) {
2475
2720
  const endnote = useInsertNote("endnote");
2476
2721
  const styles = useParagraphStyle();
2477
2722
  const editor = useDocxEditor();
2478
- const latest = useRef({ footnote, endnote, styles, editor });
2479
- latest.current = { footnote, endnote, styles, editor };
2723
+ const levels = useHouseTemplate()?.levels;
2724
+ const latest = useRef({ footnote, endnote, styles, editor, levels });
2725
+ latest.current = { footnote, endnote, styles, editor, levels };
2480
2726
  useEffect(() => {
2481
2727
  const element = scope.current;
2482
2728
  if (!element) return;
2483
2729
  const onKeyDown = (event) => {
2730
+ if (outlineTab(event)) {
2731
+ event.preventDefault();
2732
+ event.stopPropagation();
2733
+ return;
2734
+ }
2484
2735
  const action = wordShortcut(event);
2485
2736
  if (!action) return;
2486
2737
  const { footnote: footnote2, endnote: endnote2, styles: styles2, editor: editor2 } = latest.current;
@@ -2495,11 +2746,110 @@ function WordShortcuts({ scope }) {
2495
2746
  event.stopPropagation();
2496
2747
  editor2?.surface?.focus();
2497
2748
  };
2749
+ const outlineTab = (event) => {
2750
+ if (event.key !== "Tab" || event.ctrlKey || event.metaKey || event.altKey) return false;
2751
+ const { editor: editor2, levels: levels2, styles: styles2 } = latest.current;
2752
+ const current = editor2?.getSelectionFormatting()?.styleId;
2753
+ const link = current ? levels2?.[current] : void 0;
2754
+ const target = event.shiftKey ? link?.previous : link?.next;
2755
+ if (!target || !editor2 || editorPort(editor2).caret()?.offset !== 0) return false;
2756
+ styles2.setValue(target);
2757
+ return true;
2758
+ };
2498
2759
  element.addEventListener("keydown", onKeyDown, true);
2499
2760
  return () => element.removeEventListener("keydown", onKeyDown, true);
2500
2761
  }, [scope]);
2501
2762
  return null;
2502
2763
  }
2764
+ function QuickParts() {
2765
+ const house = useHouseTemplate();
2766
+ if (!house || house.blocks.length === 0) return null;
2767
+ const categories = [...new Set(house.blocks.map((b) => b.category))];
2768
+ return /* @__PURE__ */ jsx(RibbonMenu, { label: "Quick Parts", icon: /* @__PURE__ */ jsx(Blocks, { size: 16 }), testId: "docx-quick-parts", children: (close) => /* @__PURE__ */ jsxs("div", { className: "native-docx-quick-parts", "data-testid": "docx-quick-parts-panel", children: [
2769
+ categories.map((category) => /* @__PURE__ */ jsxs("section", { "aria-label": category, children: [
2770
+ /* @__PURE__ */ jsx("h3", { children: category }),
2771
+ /* @__PURE__ */ jsx("ul", { children: house.blocks.filter((b) => b.category === category).map((block) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
2772
+ "button",
2773
+ {
2774
+ type: "button",
2775
+ disabled: !house.editable,
2776
+ title: block.description || block.name,
2777
+ onClick: () => {
2778
+ close();
2779
+ house.insertBlock(block.name);
2780
+ },
2781
+ children: [
2782
+ /* @__PURE__ */ jsx("span", { className: "native-docx-quick-part-name", children: block.name }),
2783
+ /* @__PURE__ */ jsx("span", { className: "native-docx-quick-part-preview", children: block.text })
2784
+ ]
2785
+ }
2786
+ ) }, block.name)) })
2787
+ ] }, category)),
2788
+ /* @__PURE__ */ jsxs("p", { className: "native-docx-quick-parts-source", children: [
2789
+ "From ",
2790
+ house.template.name
2791
+ ] })
2792
+ ] }) });
2793
+ }
2794
+ var SOURCE = {
2795
+ workspace: "This workspace's house template",
2796
+ deployment: "The house template for all workspaces",
2797
+ builtin: "ARBI\u2019s built-in template (no house template has been set)"
2798
+ };
2799
+ function download(bytes, name) {
2800
+ const isTemplate = !/\.docx$/i.test(name);
2801
+ const url = URL.createObjectURL(
2802
+ new Blob([bytes], {
2803
+ type: isTemplate ? "application/vnd.openxmlformats-officedocument.wordprocessingml.template" : "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
2804
+ })
2805
+ );
2806
+ const a = document.createElement("a");
2807
+ a.href = url;
2808
+ a.download = /\.(dotx|docx)$/i.test(name) ? name : `${name}.dotx`;
2809
+ a.click();
2810
+ setTimeout(() => URL.revokeObjectURL(url), 5e3);
2811
+ }
2812
+ function HouseTemplatePanel() {
2813
+ const house = useHouseTemplate();
2814
+ if (!house) return null;
2815
+ const summary = divergenceSummary(house.divergence);
2816
+ return /* @__PURE__ */ jsx(
2817
+ RibbonMenu,
2818
+ {
2819
+ label: "House template",
2820
+ className: "native-docx-style-trigger",
2821
+ testId: "docx-house-template",
2822
+ icon: /* @__PURE__ */ jsxs(Fragment, { children: [
2823
+ /* @__PURE__ */ jsx("span", { className: "native-docx-style-current", children: "House template" }),
2824
+ /* @__PURE__ */ jsx(ChevronDown, { size: 12, "aria-hidden": true })
2825
+ ] }),
2826
+ children: /* @__PURE__ */ jsxs("div", { className: "native-docx-house-template", "data-testid": "docx-house-template-panel", children: [
2827
+ /* @__PURE__ */ jsxs("p", { children: [
2828
+ /* @__PURE__ */ jsx("strong", { children: house.template.name }),
2829
+ /* @__PURE__ */ jsx("br", {}),
2830
+ SOURCE[house.template.source],
2831
+ ". New documents start from it."
2832
+ ] }),
2833
+ /* @__PURE__ */ 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." }),
2834
+ /* @__PURE__ */ jsxs("div", { className: "native-docx-house-template-actions", children: [
2835
+ /* @__PURE__ */ jsxs("button", { type: "button", onClick: () => download(house.template.bytes, house.template.name), children: [
2836
+ /* @__PURE__ */ jsx(Download, { size: 14 }),
2837
+ " Download template"
2838
+ ] }),
2839
+ house.setAsHouseTemplate && /* @__PURE__ */ jsx(
2840
+ "button",
2841
+ {
2842
+ type: "button",
2843
+ "data-testid": "docx-set-house-template",
2844
+ onClick: () => void house.setAsHouseTemplate(),
2845
+ children: "Use this document as the house template"
2846
+ }
2847
+ )
2848
+ ] })
2849
+ ] })
2850
+ }
2851
+ );
2852
+ }
2503
2853
  function contentPath(documentId) {
2504
2854
  const match = /^(doc-[A-Za-z0-9]+)@v([1-9][0-9]*)$/.exec(documentId);
2505
2855
  return match ? `/${encodeURIComponent(match[1])}/content?version=${match[2]}` : `/${encodeURIComponent(documentId)}/content`;
@@ -2589,6 +2939,7 @@ function DocxRibbon({
2589
2939
  /* @__PURE__ */ jsxs("select", { "aria-label": "Ribbon tab", value: tab, onChange: (event) => setTab(event.target.value), children: [
2590
2940
  /* @__PURE__ */ jsx("option", { children: "Home" }),
2591
2941
  /* @__PURE__ */ jsx("option", { children: "Insert" }),
2942
+ /* @__PURE__ */ jsx("option", { children: "Design" }),
2592
2943
  /* @__PURE__ */ jsx("option", { children: "References" }),
2593
2944
  /* @__PURE__ */ jsx("option", { children: "Review" })
2594
2945
  ] }),
@@ -2622,8 +2973,10 @@ function DocxRibbon({
2622
2973
  /* @__PURE__ */ jsx(DocxEditor.Toolbar.TableInsert, {}),
2623
2974
  /* @__PURE__ */ jsx(DocxEditor.Toolbar.ImageInsert, {}),
2624
2975
  /* @__PURE__ */ jsx(DocxEditor.Toolbar.Link, {}),
2625
- /* @__PURE__ */ jsx(DefinedTermButtons, {})
2976
+ /* @__PURE__ */ jsx(DefinedTermButtons, {}),
2977
+ /* @__PURE__ */ jsx(QuickParts, {})
2626
2978
  ] }),
2979
+ tab === "Design" && /* @__PURE__ */ jsx(HouseTemplatePanel, {}),
2627
2980
  tab === "References" && /* @__PURE__ */ jsx(NoteButtons, {}),
2628
2981
  tab === "Review" && /* @__PURE__ */ jsxs(Fragment, { children: [
2629
2982
  /* @__PURE__ */ jsx(DocxEditor.Toolbar.EditingMode, {}),
@@ -2698,11 +3051,16 @@ function NativeDocxEditor({
2698
3051
  async function start() {
2699
3052
  const fonts = await adapter.loadFonts();
2700
3053
  if (isDraft || snapshot) {
2701
- const data = isDraft ? !streamRef.current && drafts.get(documentId) || await (await adapter.request("/prepare", {
3054
+ const data = isDraft ? !streamRef.current && drafts.get(documentId) || (draftContent === "" && adapter.houseTemplate ? await (async () => {
3055
+ const template = await resolveHouseTemplate(adapter.houseTemplate);
3056
+ return newFromTemplate(template.bytes, {
3057
+ attachedTemplate: templateFileName(template)
3058
+ });
3059
+ })() : await (await adapter.request("/prepare", {
2702
3060
  method: "POST",
2703
3061
  headers: { "Content-Type": "application/json" },
2704
3062
  body: JSON.stringify({ content: draftContent })
2705
- })).arrayBuffer() : await (await adapter.request(contentPath(documentId))).arrayBuffer();
3063
+ })).arrayBuffer()) : await (await adapter.request(contentPath(documentId))).arrayBuffer();
2706
3064
  if (!cancelled) {
2707
3065
  setReady({
2708
3066
  document: new Uint8Array(data),
@@ -2964,70 +3322,80 @@ function NativeDocxEditor({
2964
3322
  if (change.source !== "load") changed();
2965
3323
  },
2966
3324
  onFontError: (e) => setError(e.message),
2967
- children: /* @__PURE__ */ jsxs(
3325
+ children: /* @__PURE__ */ jsx(DocxAuthorContext.Provider, { value: ready.author, children: /* @__PURE__ */ jsx(
2968
3326
  DefinedTermsProvider,
2969
3327
  {
2970
3328
  load: adapter.definitions,
2971
3329
  editable: !stream && !snapshot && ready.canEdit,
2972
3330
  notify: adapter.notify,
2973
- children: [
2974
- /* @__PURE__ */ jsx(
2975
- DocxEditor.RevisionMarkup,
2976
- {
2977
- insertions: { mark: "underline", color: "blue" },
2978
- deletions: { mark: "strikethrough", color: "red" },
2979
- movedFrom: { mark: "doubleStrikethrough", color: "green" },
2980
- movedTo: { mark: "doubleUnderline", color: "green" },
2981
- trackMoves: true
2982
- }
2983
- ),
2984
- !stream && /* @__PURE__ */ jsx(
2985
- DocxRibbon,
2986
- {
2987
- onSave: () => void saveVersion(),
2988
- badge: (
2989
- // Says the document is shared live, in place of a title row above the ribbon.
2990
- ready.room && ready.canEdit && !snapshot ? /* @__PURE__ */ jsx("span", { title: "Edits sync live with everyone who has this document open", children: "Co-edit mode" }) : void 0
3331
+ children: /* @__PURE__ */ jsxs(
3332
+ HouseTemplateProvider,
3333
+ {
3334
+ enabled: !stream,
3335
+ host: adapter.houseTemplate,
3336
+ documentId: isDraft || snapshot ? void 0 : documentId,
3337
+ editable: !stream && !snapshot && ready.canEdit,
3338
+ notify: adapter.notify,
3339
+ children: [
3340
+ /* @__PURE__ */ jsx(
3341
+ DocxEditor.RevisionMarkup,
3342
+ {
3343
+ insertions: { mark: "underline", color: "blue" },
3344
+ deletions: { mark: "strikethrough", color: "red" },
3345
+ movedFrom: { mark: "doubleStrikethrough", color: "green" },
3346
+ movedTo: { mark: "doubleUnderline", color: "green" },
3347
+ trackMoves: true
3348
+ }
3349
+ ),
3350
+ !stream && /* @__PURE__ */ jsx(
3351
+ DocxRibbon,
3352
+ {
3353
+ onSave: () => void saveVersion(),
3354
+ badge: (
3355
+ // Says the document is shared live, in place of a title row above the ribbon.
3356
+ ready.room && ready.canEdit && !snapshot ? /* @__PURE__ */ jsx("span", { title: "Edits sync live with everyone who has this document open", children: "Co-edit mode" }) : void 0
3357
+ )
3358
+ }
3359
+ ),
3360
+ !stream && /* @__PURE__ */ jsx(WordShortcuts, { scope: stageRef }),
3361
+ /* @__PURE__ */ jsxs(
3362
+ "div",
3363
+ {
3364
+ className: "native-docx-stage",
3365
+ ref: stageRef,
3366
+ onMouseDownCapture: (event) => {
3367
+ if (event.button === 2 && ref.current?.getSelectionPlacement())
3368
+ event.preventDefault();
3369
+ },
3370
+ children: [
3371
+ /* @__PURE__ */ jsxs(DocxEditor.Viewport, { className: "native-docx-viewport", children: [
3372
+ /* @__PURE__ */ jsx(DocxEditor.Content, {}),
3373
+ /* @__PURE__ */ jsx(
3374
+ DocxMentions,
3375
+ {
3376
+ initialCommentId,
3377
+ mentions: adapter.mentions,
3378
+ notify: adapter.notify,
3379
+ getAgentStatus: adapter.getAgentStatus,
3380
+ requestAgent: adapter.requestAgent ? (request) => adapter.requestAgent(documentId, request) : void 0,
3381
+ children: /* @__PURE__ */ jsx(DocxEditorReview, { children: () => /* @__PURE__ */ jsx(DocxEditorReview.Card, { children: /* @__PURE__ */ jsx(DocxCommentActivity, {}) }) })
3382
+ }
3383
+ ),
3384
+ /* @__PURE__ */ jsx(DocxEditor.HeaderFooterChrome, {}),
3385
+ /* @__PURE__ */ jsx(DocxEditor.NotesChrome, {}),
3386
+ /* @__PURE__ */ jsx(DocxEditor.Equation, {}),
3387
+ /* @__PURE__ */ jsx(DocxEditor.HyperLink, {}),
3388
+ /* @__PURE__ */ jsx(DocxEditor.ContextMenu, {})
3389
+ ] }),
3390
+ /* @__PURE__ */ jsx(DocxEditor.PageNumber, {})
3391
+ ]
3392
+ }
2991
3393
  )
2992
- }
2993
- ),
2994
- !stream && /* @__PURE__ */ jsx(WordShortcuts, { scope: stageRef }),
2995
- /* @__PURE__ */ jsxs(
2996
- "div",
2997
- {
2998
- className: "native-docx-stage",
2999
- ref: stageRef,
3000
- onMouseDownCapture: (event) => {
3001
- if (event.button === 2 && ref.current?.getSelectionPlacement())
3002
- event.preventDefault();
3003
- },
3004
- children: [
3005
- /* @__PURE__ */ jsxs(DocxEditor.Viewport, { className: "native-docx-viewport", children: [
3006
- /* @__PURE__ */ jsx(DocxEditor.Content, {}),
3007
- /* @__PURE__ */ jsx(
3008
- DocxMentions,
3009
- {
3010
- initialCommentId,
3011
- mentions: adapter.mentions,
3012
- notify: adapter.notify,
3013
- getAgentStatus: adapter.getAgentStatus,
3014
- requestAgent: adapter.requestAgent ? (request) => adapter.requestAgent(documentId, request) : void 0,
3015
- children: /* @__PURE__ */ jsx(DocxEditorReview, { children: () => /* @__PURE__ */ jsx(DocxEditorReview.Card, { children: /* @__PURE__ */ jsx(DocxCommentActivity, {}) }) })
3016
- }
3017
- ),
3018
- /* @__PURE__ */ jsx(DocxEditor.HeaderFooterChrome, {}),
3019
- /* @__PURE__ */ jsx(DocxEditor.NotesChrome, {}),
3020
- /* @__PURE__ */ jsx(DocxEditor.Equation, {}),
3021
- /* @__PURE__ */ jsx(DocxEditor.HyperLink, {}),
3022
- /* @__PURE__ */ jsx(DocxEditor.ContextMenu, {})
3023
- ] }),
3024
- /* @__PURE__ */ jsx(DocxEditor.PageNumber, {})
3025
- ]
3026
- }
3027
- )
3028
- ]
3394
+ ]
3395
+ }
3396
+ )
3029
3397
  }
3030
- )
3398
+ ) })
3031
3399
  }
3032
3400
  ) }, retry) }),
3033
3401
  /* @__PURE__ */ jsxs("div", { className: "native-docx-actions", children: [
@@ -3122,6 +3490,6 @@ async function loadFonts(baseUrl) {
3122
3490
  };
3123
3491
  }
3124
3492
 
3125
- export { DEFAULT_DEFINED_TERMS_LABELS, DEFINED_TERM_TRIGGER, DefinedTermsProvider, DocxCommentActivity, DocxEditorReview, DocxMentions, DocxParticipants, DocxRibbon, NativeDocxEditor, editorPort, findDefinitionsSection, insertDefinedTerm, loadDocxFonts, normalizeDocxExport, reviewModule, updateDefinitions, useDefinedTerms, useReview, useReviewItem, withDirectEditing };
3126
- //# sourceMappingURL=chunk-QIEEFBZ5.js.map
3127
- //# sourceMappingURL=chunk-QIEEFBZ5.js.map
3493
+ export { BUILTIN_TEMPLATE_NAME, DEFAULT_DEFINED_TERMS_LABELS, DEFINED_TERM_TRIGGER, DefinedTermsProvider, DocxAuthorContext, DocxCommentActivity, DocxEditorReview, DocxMentions, DocxParticipants, DocxRibbon, HouseTemplateProvider, NativeDocxEditor, builtinHouseTemplate, contentPath, editorPort, findDefinitionsSection, insertDefinedTerm, loadDocxFonts, normalizeDocxExport, resolveHouseTemplate, reviewModule, structuralPort, templateFileName, trackedOrNoted, updateDefinitions, useDefinedTerms, useHouseTemplate, useReview, useReviewItem, useTrackedOrNoted, withDirectEditing };
3494
+ //# sourceMappingURL=chunk-4ZTIMQ7Z.js.map
3495
+ //# sourceMappingURL=chunk-4ZTIMQ7Z.js.map