@arbidocs/blocks 0.4.20 → 0.4.22

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 (93) hide show
  1. package/dist/agents/definitions/AgentCard.d.ts +4 -1
  2. package/dist/agents/index.cjs +211 -211
  3. package/dist/agents/index.js +5 -5
  4. package/dist/chat/ChatPage.d.ts +1 -1
  5. package/dist/chat/chat-page-types.d.ts +2 -0
  6. package/dist/chat/index.cjs +169 -168
  7. package/dist/chat/index.js +3 -2
  8. package/dist/chunk-5TRALRWN.js +42 -0
  9. package/dist/chunk-5TRALRWN.js.map +1 -0
  10. package/dist/chunk-7UTB4PHM.cjs +300 -0
  11. package/dist/chunk-7UTB4PHM.cjs.map +1 -0
  12. package/dist/chunk-AA3PT6KR.js +286 -0
  13. package/dist/chunk-AA3PT6KR.js.map +1 -0
  14. package/dist/{chunk-QJEJBLHI.js → chunk-CQ6CYPV4.js} +7 -281
  15. package/dist/chunk-CQ6CYPV4.js.map +1 -0
  16. package/dist/chunk-EUOGPUMJ.cjs +59 -0
  17. package/dist/chunk-EUOGPUMJ.cjs.map +1 -0
  18. package/dist/{chunk-PRVKJ5XL.cjs → chunk-J22DXUJY.cjs} +4 -3
  19. package/dist/chunk-J22DXUJY.cjs.map +1 -0
  20. package/dist/{chunk-ZPOLKYR2.cjs → chunk-JCVZKCXC.cjs} +22 -69
  21. package/dist/chunk-JCVZKCXC.cjs.map +1 -0
  22. package/dist/{chunk-557EU3G4.js → chunk-JLOYWXRB.js} +3 -3
  23. package/dist/{chunk-557EU3G4.js.map → chunk-JLOYWXRB.js.map} +1 -1
  24. package/dist/{chunk-UVIOQUJA.js → chunk-JLPYOPCX.js} +4 -3
  25. package/dist/chunk-JLPYOPCX.js.map +1 -0
  26. package/dist/{chunk-4TBZRZFC.js → chunk-MLJDOH3B.js} +23 -6
  27. package/dist/chunk-MLJDOH3B.js.map +1 -0
  28. package/dist/chunk-NNH7N62N.cjs +876 -0
  29. package/dist/chunk-NNH7N62N.cjs.map +1 -0
  30. package/dist/{chunk-7Q6RBXIV.cjs → chunk-OEQXYWUO.cjs} +22 -5
  31. package/dist/chunk-OEQXYWUO.cjs.map +1 -0
  32. package/dist/{chunk-J4AOAFZF.cjs → chunk-TL2DHSC5.cjs} +11 -297
  33. package/dist/chunk-TL2DHSC5.cjs.map +1 -0
  34. package/dist/chunk-UOLMFC5K.js +846 -0
  35. package/dist/chunk-UOLMFC5K.js.map +1 -0
  36. package/dist/{chunk-DCEI37AX.cjs → chunk-YVIOCITQ.cjs} +3 -3
  37. package/dist/{chunk-DCEI37AX.cjs.map → chunk-YVIOCITQ.cjs.map} +1 -1
  38. package/dist/{chunk-S74WZNK7.js → chunk-ZEIOREJY.js} +5 -36
  39. package/dist/chunk-ZEIOREJY.js.map +1 -0
  40. package/dist/connect/docx.cjs +86 -0
  41. package/dist/connect/docx.cjs.map +1 -0
  42. package/dist/connect/docx.d.ts +1 -0
  43. package/dist/connect/docx.js +84 -0
  44. package/dist/connect/docx.js.map +1 -0
  45. package/dist/connect/index.cjs +136 -69
  46. package/dist/connect/index.cjs.map +1 -1
  47. package/dist/connect/index.d.ts +1 -0
  48. package/dist/connect/index.js +91 -25
  49. package/dist/connect/index.js.map +1 -1
  50. package/dist/connection/ConnectedNativeDocxEditor.d.ts +5 -0
  51. package/dist/connection/useConnectedDocxNotifications.d.ts +9 -0
  52. package/dist/docx/DocxMentions.d.ts +17 -0
  53. package/dist/docx/NativeDocxComparison.d.ts +10 -0
  54. package/dist/docx/NativeDocxEditor.d.ts +50 -0
  55. package/dist/docx/export.d.ts +2 -0
  56. package/dist/docx/fonts.d.ts +2 -0
  57. package/dist/docx/index.cjs +116 -0
  58. package/dist/docx/index.cjs.map +1 -0
  59. package/dist/docx/index.d.ts +10 -0
  60. package/dist/docx/index.js +63 -0
  61. package/dist/docx/index.js.map +1 -0
  62. package/dist/docx/mentions.d.ts +8 -0
  63. package/dist/facts/index.cjs +1 -1
  64. package/dist/facts/index.js +1 -1
  65. package/dist/files/index.cjs +5 -5
  66. package/dist/files/index.js +6 -6
  67. package/dist/index.cjs +795 -794
  68. package/dist/index.cjs.map +1 -1
  69. package/dist/index.js +17 -16
  70. package/dist/index.js.map +1 -1
  71. package/dist/settings/index.cjs +41 -41
  72. package/dist/settings/index.js +4 -4
  73. package/dist/shell/index.cjs +9 -9
  74. package/dist/shell/index.js +1 -1
  75. package/dist/site/index.cjs +13 -13
  76. package/dist/site/index.js +1 -1
  77. package/dist/studio/index.cjs +6 -5
  78. package/dist/studio/index.cjs.map +1 -1
  79. package/dist/studio/index.js +4 -3
  80. package/dist/studio/index.js.map +1 -1
  81. package/dist/workspaces/index.cjs +2 -2
  82. package/dist/workspaces/index.js +2 -2
  83. package/package.json +48 -7
  84. package/src/docx/README.md +68 -0
  85. package/src/docx/styles.css +36 -0
  86. package/dist/chunk-4TBZRZFC.js.map +0 -1
  87. package/dist/chunk-7Q6RBXIV.cjs.map +0 -1
  88. package/dist/chunk-J4AOAFZF.cjs.map +0 -1
  89. package/dist/chunk-PRVKJ5XL.cjs.map +0 -1
  90. package/dist/chunk-QJEJBLHI.js.map +0 -1
  91. package/dist/chunk-S74WZNK7.js.map +0 -1
  92. package/dist/chunk-UVIOQUJA.js.map +0 -1
  93. package/dist/chunk-ZPOLKYR2.cjs.map +0 -1
@@ -0,0 +1,846 @@
1
+ "use client";
2
+ import { filterMentionCandidates, MentionMenu, findMentionQuery, extractMentions } from './chunk-AA3PT6KR.js';
3
+ import { unzipSync, strFromU8, strToU8, zipSync } from 'fflate';
4
+ import React2, { useRef, useEffect, useState, useLayoutEffect } from 'react';
5
+ import { useReview, DocxEditorReview, reviewModule } from '@docx-editor.dev/pro/react';
6
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
7
+ import * as Y from 'yjs';
8
+ import { useChromeTranslate, DocxEditor, createFontSource } from '@docx-editor.dev/react';
9
+ import { collaborationModule } from '@docx-editor.dev/pro';
10
+ import { DOCUMENT_COLLABORATION_VERSIONS } from '@docx-editor.dev/pro/collaboration';
11
+ import { createHocuspocusCollaboration } from '@docx-editor.dev/pro/collaboration/hocuspocus';
12
+ import { Type, Ellipsis, WifiOff, Check, Download, Save, RefreshCw } from 'lucide-react';
13
+
14
+ function normalizeDocxExport(data) {
15
+ const files = unzipSync(new Uint8Array(data));
16
+ const part = "[Content_Types].xml";
17
+ const xml = new DOMParser().parseFromString(strFromU8(files[part]), "application/xml");
18
+ if (xml.getElementsByTagName("parsererror").length) throw new Error("Invalid DOCX content types.");
19
+ const namespace = "http://schemas.openxmlformats.org/package/2006/content-types";
20
+ const types = {
21
+ "word/comments.xml": "application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml",
22
+ "word/commentsExtended.xml": "application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtended+xml",
23
+ "word/commentsIds.xml": "application/vnd.openxmlformats-officedocument.wordprocessingml.commentsIds+xml",
24
+ "word/commentsExtensible.xml": "application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtensible+xml"
25
+ };
26
+ let changed = false;
27
+ for (const [name, type] of Object.entries(types)) {
28
+ if (!files[name]) continue;
29
+ let override = Array.from(xml.getElementsByTagNameNS(namespace, "Override")).find(
30
+ (node) => node.getAttribute("PartName") === "/" + name
31
+ );
32
+ if (!override) {
33
+ override = xml.createElementNS(namespace, "Override");
34
+ override.setAttribute("PartName", "/" + name);
35
+ xml.documentElement.appendChild(override);
36
+ }
37
+ if (override.getAttribute("ContentType") !== type) {
38
+ override.setAttribute("ContentType", type);
39
+ changed = true;
40
+ }
41
+ }
42
+ if (changed) files[part] = strToU8(new XMLSerializer().serializeToString(xml));
43
+ const word = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
44
+ for (const name of Object.keys(files)) {
45
+ if (!/^word\/(document|header\d*|footer\d*|footnotes|endnotes|comments)\.xml$/.test(name))
46
+ continue;
47
+ const document2 = new DOMParser().parseFromString(strFromU8(files[name]), "application/xml");
48
+ if (document2.getElementsByTagName("parsererror").length)
49
+ throw new Error("Invalid Word document XML.");
50
+ const elements = Array.from(document2.getElementsByTagName("*"));
51
+ const used = new Set(
52
+ elements.map((element) => element.getAttributeNS(word, "id")).filter(Boolean)
53
+ );
54
+ const seen = /* @__PURE__ */ new Set();
55
+ let next = 1, repaired = false;
56
+ for (const element of elements) {
57
+ if (element.namespaceURI !== word || !["ins", "del", "moveFrom", "moveTo"].includes(element.localName))
58
+ continue;
59
+ const id = element.getAttributeNS(word, "id");
60
+ if (!id) continue;
61
+ if (seen.has(id)) {
62
+ while (used.has(String(next))) next++;
63
+ const unique = String(next++);
64
+ const attribute = element.getAttributeNodeNS(word, "id");
65
+ element.setAttributeNS(word, attribute.name, unique);
66
+ used.add(unique);
67
+ repaired = true;
68
+ } else seen.add(id);
69
+ }
70
+ if (repaired) {
71
+ files[name] = strToU8(new XMLSerializer().serializeToString(document2));
72
+ changed = true;
73
+ }
74
+ }
75
+ if (!changed) return data;
76
+ return zipSync(files).slice().buffer;
77
+ }
78
+
79
+ // src/docx/mentions.ts
80
+ function mentionCandidates(mentions) {
81
+ return [
82
+ { id: "arbi", label: "ARBI", kind: "assistant" },
83
+ ...mentions.filter((person) => person.name.trim()).map((person) => ({
84
+ id: person.id,
85
+ label: person.name,
86
+ kind: person.kind === "agent" ? "assistant" : "user"
87
+ }))
88
+ ];
89
+ }
90
+ function mentionedDocxRecipients(text, mentions) {
91
+ return extractMentions(text, mentionCandidates(mentions)).map((person) => person.id);
92
+ }
93
+ function DocxMentions({
94
+ children,
95
+ mentions = [],
96
+ initialCommentId,
97
+ requestAgent,
98
+ notify
99
+ }) {
100
+ const review = useReview();
101
+ const activated = useRef(void 0);
102
+ useEffect(() => {
103
+ if (!initialCommentId || activated.current === initialCommentId || !review.ready) return;
104
+ const target = review.items.find(
105
+ (item) => item.kind === "comment" && item.item.id === initialCommentId
106
+ );
107
+ if (!target) return;
108
+ review.setPaneOpen(true);
109
+ if (review.setActive(target.key)) activated.current = initialCommentId;
110
+ }, [initialCommentId, review]);
111
+ const input = useRef(null);
112
+ const replyTarget = useRef(null);
113
+ const pending = useRef(null);
114
+ const [suggestions, setSuggestions] = useState(null);
115
+ const [selected, setSelected] = useState(0);
116
+ const candidates = filterMentionCandidates(
117
+ mentionCandidates(mentions),
118
+ suggestions?.query || ""
119
+ ).slice(0, 8);
120
+ useEffect(() => {
121
+ const submission = pending.current;
122
+ if (!submission || !requestAgent) return;
123
+ const created = review.items.find(
124
+ (item) => item.kind === "comment" && !submission.ids.has(item.item.id) && item.text.trim() === submission.text
125
+ );
126
+ if (!created || created.kind !== "comment") return;
127
+ pending.current = null;
128
+ const commentId = created.parentId || created.item.id;
129
+ void requestAgent({
130
+ commentId,
131
+ instruction: submission.text,
132
+ requestId: submission.requestId,
133
+ recipientIds: submission.recipientIds
134
+ }).catch((error) => {
135
+ notify?.(
136
+ error instanceof Error ? error.message : "Could not start the document agent.",
137
+ "error"
138
+ );
139
+ });
140
+ }, [review.items, requestAgent, notify]);
141
+ const choose = (name) => {
142
+ const field = input.current;
143
+ if (!field || !suggestions) return;
144
+ const end = field.selectionStart ?? field.value.length;
145
+ const value = field.value.slice(0, suggestions.from) + "@" + name + " " + field.value.slice(end);
146
+ Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set?.call(field, value);
147
+ field.dispatchEvent(new Event("input", { bubbles: true }));
148
+ field.focus();
149
+ field.setSelectionRange(suggestions.from + name.length + 2, suggestions.from + name.length + 2);
150
+ setSuggestions(null);
151
+ };
152
+ const captureSubmission = (target) => {
153
+ const form = target.closest("form");
154
+ const field = form?.querySelector(
155
+ '[data-testid="review-draft-input"], [data-testid="review-reply-input"]'
156
+ );
157
+ if (!field || field.readOnly || !requestAgent) return false;
158
+ const recipientIds = mentionedDocxRecipients(field.value, mentions);
159
+ if (!recipientIds.length) return false;
160
+ pending.current = {
161
+ ids: new Set(review.items.filter((i) => i.kind === "comment").map((i) => i.item.id)),
162
+ text: field.value.trim(),
163
+ requestId: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`,
164
+ recipientIds
165
+ };
166
+ const pinned = replyTarget.current;
167
+ const thread = pinned ? review.items.find((item) => item.key === pinned.key && item.kind === "comment") : void 0;
168
+ if (field.dataset.testid === "review-reply-input" && pinned && !thread) {
169
+ pending.current = null;
170
+ notify?.("This comment was removed. Open a current thread before replying.", "error");
171
+ return true;
172
+ }
173
+ if (field.dataset.testid === "review-reply-input" && thread) {
174
+ if (!review.reply(thread, field.value.trim())) {
175
+ pending.current = null;
176
+ notify?.("Could not post this reply. The comment may have been removed.", "error");
177
+ } else {
178
+ Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set?.call(field, "");
179
+ field.dispatchEvent(new Event("input", { bubbles: true }));
180
+ replyTarget.current = null;
181
+ review.setActive(thread.key, { reveal: false });
182
+ }
183
+ return true;
184
+ }
185
+ return false;
186
+ };
187
+ return /* @__PURE__ */ jsxs(
188
+ "div",
189
+ {
190
+ className: "native-docx-mentions",
191
+ onInputCapture: (event) => {
192
+ const field = event.target;
193
+ if (!(field instanceof HTMLInputElement) || !field.dataset.testid?.startsWith("review-"))
194
+ return;
195
+ if (!replyTarget.current && review.activeKey)
196
+ replyTarget.current = { field, key: review.activeKey };
197
+ input.current = field;
198
+ const before = field.value.slice(0, field.selectionStart ?? field.value.length);
199
+ const match = findMentionQuery(before, before.length);
200
+ if (!match) return setSuggestions(null);
201
+ const rect = field.getBoundingClientRect();
202
+ setSelected(0);
203
+ setSuggestions({
204
+ query: match.query,
205
+ from: match.start,
206
+ left: rect.left,
207
+ top: rect.bottom
208
+ });
209
+ },
210
+ onPointerDownCapture: (event) => {
211
+ const target = event.target;
212
+ if (target instanceof HTMLElement && target.closest('[data-testid="review-marker"], [data-testid="review-reply-cancel"]'))
213
+ replyTarget.current = null;
214
+ },
215
+ onSubmitCapture: (event) => {
216
+ if (captureSubmission(event.target)) {
217
+ event.preventDefault();
218
+ event.stopPropagation();
219
+ }
220
+ },
221
+ onKeyDownCapture: (event) => {
222
+ if (suggestions && candidates.length) {
223
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
224
+ event.preventDefault();
225
+ event.stopPropagation();
226
+ setSelected(
227
+ (s) => (s + (event.key === "ArrowDown" ? 1 : candidates.length - 1)) % candidates.length
228
+ );
229
+ return;
230
+ }
231
+ if (event.key === "Enter" || event.key === "Tab") {
232
+ event.preventDefault();
233
+ event.stopPropagation();
234
+ choose(candidates[selected]?.label || candidates[0].label);
235
+ return;
236
+ }
237
+ if (event.key === "Escape") {
238
+ event.preventDefault();
239
+ event.stopPropagation();
240
+ setSuggestions(null);
241
+ return;
242
+ }
243
+ }
244
+ if (event.key === "Enter" && captureSubmission(event.target)) {
245
+ event.preventDefault();
246
+ event.stopPropagation();
247
+ }
248
+ },
249
+ children: [
250
+ children,
251
+ suggestions && candidates.length > 0 && /* @__PURE__ */ jsx(
252
+ "div",
253
+ {
254
+ onPointerDown: (event) => event.stopPropagation(),
255
+ onMouseDown: (event) => {
256
+ event.preventDefault();
257
+ event.stopPropagation();
258
+ },
259
+ onClick: (event) => event.stopPropagation(),
260
+ style: {
261
+ position: "fixed",
262
+ left: suggestions.left,
263
+ top: suggestions.top,
264
+ width: 280,
265
+ zIndex: 1e3
266
+ },
267
+ children: /* @__PURE__ */ jsx(
268
+ MentionMenu,
269
+ {
270
+ open: true,
271
+ placement: "below",
272
+ items: candidates,
273
+ selectedIndex: selected,
274
+ setSelectedIndex: setSelected,
275
+ onSelect: (person) => choose(person.label),
276
+ labels: { list: "Mention workspace member" }
277
+ }
278
+ )
279
+ }
280
+ )
281
+ ]
282
+ }
283
+ );
284
+ }
285
+ var drafts = /* @__PURE__ */ new Map();
286
+ var stateBytes = (room) => Y.encodeStateAsUpdate(room.ydoc);
287
+ var encodeState = (room) => {
288
+ let binary = "";
289
+ for (const byte of stateBytes(room)) binary += String.fromCharCode(byte);
290
+ return btoa(binary);
291
+ };
292
+ function covered(room, vector, receipt) {
293
+ const decode = (value) => Uint8Array.from(atob(value), (c) => c.charCodeAt(0));
294
+ const server = Y.decodeStateVector(decode(vector)), local = Y.decodeStateVector(Y.encodeStateVector(room.ydoc));
295
+ for (const [client, clock] of local) if ((server.get(client) || 0) < clock) return false;
296
+ const deleted = Y.decodeUpdate(stateBytes(room)).ds.clients;
297
+ const saved = Y.decodeUpdate(decode(receipt)).ds.clients;
298
+ for (const [client, ranges] of deleted)
299
+ for (const range of ranges) {
300
+ if (!(saved.get(client) || []).some(
301
+ (s) => s.clock <= range.clock && s.clock + s.len >= range.clock + range.len
302
+ ))
303
+ return false;
304
+ }
305
+ return true;
306
+ }
307
+ var EditorBoundary = class extends React2.Component {
308
+ state = {};
309
+ static getDerivedStateFromError(error) {
310
+ return { error };
311
+ }
312
+ render() {
313
+ return this.state.error ? /* @__PURE__ */ jsxs("div", { role: "alert", className: "native-docx-error", children: [
314
+ "Could not render this Word document: ",
315
+ this.state.error.message
316
+ ] }) : this.props.children;
317
+ }
318
+ };
319
+ function DocxParticipants({ session }) {
320
+ const [people, setPeople] = useState([]);
321
+ useEffect(() => {
322
+ if (!session) return;
323
+ setPeople(session.participants());
324
+ return session.subscribeParticipants(setPeople);
325
+ }, [session]);
326
+ return /* @__PURE__ */ jsx("div", { className: "native-docx-presence", "aria-label": "People viewing this document", children: people.map((p) => /* @__PURE__ */ jsx("span", { className: "native-docx-avatar", title: p.name, children: p.name.split(" ").map((s) => s[0]).slice(0, 2).join("") }, p.actorId)) });
327
+ }
328
+ function RibbonMenu({
329
+ label,
330
+ icon,
331
+ children
332
+ }) {
333
+ const [open, setOpen] = useState(false);
334
+ const element = useRef(null);
335
+ useEffect(() => {
336
+ if (!open) return;
337
+ const dismiss = (event) => {
338
+ if (!element.current?.contains(event.target)) setOpen(false);
339
+ };
340
+ const escape = (event) => {
341
+ if (event.key === "Escape") setOpen(false);
342
+ };
343
+ document.addEventListener("pointerdown", dismiss);
344
+ document.addEventListener("keydown", escape);
345
+ return () => {
346
+ document.removeEventListener("pointerdown", dismiss);
347
+ document.removeEventListener("keydown", escape);
348
+ };
349
+ }, [open]);
350
+ return /* @__PURE__ */ jsxs("div", { className: "native-docx-ribbon-menu", ref: element, children: [
351
+ /* @__PURE__ */ jsx(
352
+ "button",
353
+ {
354
+ type: "button",
355
+ className: "native-docx-ribbon-trigger",
356
+ "aria-label": label,
357
+ title: label,
358
+ "aria-expanded": open,
359
+ onMouseDown: (event) => event.preventDefault(),
360
+ onClick: () => setOpen((value) => !value),
361
+ children: icon
362
+ }
363
+ ),
364
+ open && /* @__PURE__ */ jsx(
365
+ "div",
366
+ {
367
+ className: "native-docx-ribbon-popover",
368
+ role: "group",
369
+ "aria-label": label,
370
+ onMouseDown: (event) => {
371
+ if (!(event.target instanceof HTMLInputElement || event.target instanceof HTMLSelectElement))
372
+ event.preventDefault();
373
+ },
374
+ children
375
+ }
376
+ )
377
+ ] });
378
+ }
379
+ function DocxRibbon({ onSave }) {
380
+ const [tab, setTab] = useState("Home");
381
+ const t = useChromeTranslate();
382
+ return /* @__PURE__ */ jsxs("div", { className: "native-docx-ribbon", children: [
383
+ /* @__PURE__ */ jsxs("select", { "aria-label": "Ribbon tab", value: tab, onChange: (event) => setTab(event.target.value), children: [
384
+ /* @__PURE__ */ jsx("option", { children: "Home" }),
385
+ /* @__PURE__ */ jsx("option", { children: "Insert" }),
386
+ /* @__PURE__ */ jsx("option", { children: "Review" })
387
+ ] }),
388
+ /* @__PURE__ */ jsxs(DocxEditor.Toolbar, { t, preset: false, onSave, children: [
389
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.Undo, {}),
390
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.Redo, {}),
391
+ tab === "Home" && /* @__PURE__ */ jsxs(Fragment, { children: [
392
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.Bold, {}),
393
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.Italic, {}),
394
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.Underline, {}),
395
+ /* @__PURE__ */ jsxs(RibbonMenu, { label: "Font", icon: /* @__PURE__ */ jsx(Type, { size: 16 }), children: [
396
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.FontFamily, {}),
397
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.FontSize, {}),
398
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.FontColor, {}),
399
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.Highlight, {})
400
+ ] }),
401
+ /* @__PURE__ */ jsxs(RibbonMenu, { label: "More formatting", icon: /* @__PURE__ */ jsx(Ellipsis, { size: 16 }), children: [
402
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.StylePicker, {}),
403
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.Alignment, {}),
404
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.BulletList, {}),
405
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.NumberedList, {}),
406
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.LineSpacing, {}),
407
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.Indent, {}),
408
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.Outdent, {}),
409
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.Strike, {}),
410
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.ClearFormatting, {})
411
+ ] })
412
+ ] }),
413
+ tab === "Insert" && /* @__PURE__ */ jsxs(Fragment, { children: [
414
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.TableInsert, {}),
415
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.ImageInsert, {}),
416
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.Link, {})
417
+ ] }),
418
+ tab === "Review" && /* @__PURE__ */ jsxs(Fragment, { children: [
419
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.EditingMode, {}),
420
+ /* @__PURE__ */ jsx(DocxEditor.Toolbar.Comments, {}),
421
+ /* @__PURE__ */ jsx(RibbonMenu, { label: "Review options", icon: /* @__PURE__ */ jsx(Ellipsis, { size: 16 }), children: /* @__PURE__ */ jsx(DocxEditor.Toolbar.Reviewers, {}) })
422
+ ] })
423
+ ] })
424
+ ] });
425
+ }
426
+ var fixedZoomMode = { type: "fixed" };
427
+ function NativeDocxEditor({
428
+ documentId,
429
+ title,
430
+ draftContent,
431
+ initialCommentId,
432
+ snapshot = false,
433
+ onSaved,
434
+ adapter
435
+ }) {
436
+ const [ready, setReady] = useState(), [error, setError] = useState(""), [retry, setRetry] = useState(0);
437
+ const [status, setStatus] = useState("Connecting\u2026"), [dirty, setDirty] = useState(false), [saving, setSaving] = useState(false), [offline, setOffline] = useState(false);
438
+ const revision = useRef(0);
439
+ const stageRef = useRef(null), fitCleanup = useRef(void 0);
440
+ const ref = useRef(null), dirtyRef = useRef(false), draftTimer = useRef(void 0);
441
+ const isDraft = draftContent !== void 0;
442
+ useEffect(() => {
443
+ let cancelled = false;
444
+ let room;
445
+ const cleanups = [];
446
+ setReady(void 0);
447
+ setError("");
448
+ setStatus("Connecting\u2026");
449
+ setDirty(false);
450
+ dirtyRef.current = false;
451
+ async function start() {
452
+ const fonts = await adapter.loadFonts();
453
+ if (isDraft || snapshot) {
454
+ const data = isDraft ? drafts.get(documentId) || await (await adapter.request("/prepare", {
455
+ method: "POST",
456
+ headers: { "Content-Type": "application/json" },
457
+ body: JSON.stringify({ content: draftContent })
458
+ })).arrayBuffer() : await (await adapter.request(`/${documentId}/content`)).arrayBuffer();
459
+ if (!cancelled) {
460
+ setReady({
461
+ document: new Uint8Array(data),
462
+ fonts,
463
+ modules: [reviewModule()],
464
+ canEdit: !snapshot,
465
+ author: adapter.author || "You",
466
+ filename: title
467
+ });
468
+ setStatus(snapshot ? "Saved version \xB7 read only" : "Unsaved note");
469
+ }
470
+ return;
471
+ }
472
+ const session = await adapter.open(documentId);
473
+ const ws = new URL(session.websocket_path, adapter.baseUrl || location.origin);
474
+ ws.protocol = ws.protocol === "https:" ? "wss:" : "ws:";
475
+ let initial = true;
476
+ room = await createHocuspocusCollaboration({
477
+ url: ws.toString(),
478
+ roomId: session.room_id,
479
+ identity: session.identity,
480
+ bootstrap: { kind: "join" },
481
+ offlineEditing: true,
482
+ token: async () => {
483
+ const next = initial ? session : await adapter.open(documentId);
484
+ initial = false;
485
+ if (!cancelled)
486
+ setReady((previous) => previous ? { ...previous, canEdit: next.can_edit } : previous);
487
+ return JSON.stringify({
488
+ capability: next.token,
489
+ versions: DOCUMENT_COLLABORATION_VERSIONS
490
+ });
491
+ }
492
+ });
493
+ if (cancelled) {
494
+ room.destroy();
495
+ return;
496
+ }
497
+ const current = room;
498
+ cleanups.push(
499
+ current.session.subscribeStatus((s, _reason, detail) => {
500
+ const disconnected = s === "disconnected";
501
+ setOffline(disconnected);
502
+ if (s === "error") {
503
+ setError(
504
+ detail || "The collaboration session failed. Export your local document before reconnecting."
505
+ );
506
+ setStatus("Connection failed");
507
+ } else if (disconnected) setStatus("Offline \xB7 edits buffered locally");
508
+ else if (s === "ready") {
509
+ setError("");
510
+ setStatus(dirtyRef.current ? "Saving changes\u2026" : "Connected");
511
+ }
512
+ })
513
+ );
514
+ const stateless = ({ payload }) => {
515
+ try {
516
+ const message = JSON.parse(payload);
517
+ if (message.type === "persisted") {
518
+ if (!cancelled && message.vector && message.receipt && covered(current, message.vector, message.receipt)) {
519
+ dirtyRef.current = false;
520
+ setDirty(false);
521
+ setStatus("All changes saved");
522
+ }
523
+ }
524
+ if (message.type === "pending") setStatus("Saving changes\u2026");
525
+ if (message.type === "save-error") setStatus(message.message || "Autosave failed");
526
+ } catch {
527
+ }
528
+ };
529
+ current.provider.on("stateless", stateless);
530
+ cleanups.push(() => current.provider.off("stateless", stateless));
531
+ if (adapter.subscribeTokenChanges)
532
+ cleanups.push(
533
+ adapter.subscribeTokenChanges(() => {
534
+ void current.provider.sendToken().catch(() => setStatus("Could not renew the collaboration session"));
535
+ })
536
+ );
537
+ const refresh = setInterval(
538
+ () => {
539
+ void current.provider.sendToken().catch(() => setStatus("Could not renew the collaboration session"));
540
+ },
541
+ 5 * 60 * 1e3
542
+ );
543
+ cleanups.push(() => clearInterval(refresh));
544
+ setReady({
545
+ document: current.document,
546
+ fonts,
547
+ modules: [reviewModule(), collaborationModule({ session: current.session })],
548
+ room: current,
549
+ canEdit: session.can_edit,
550
+ author: session.identity.name,
551
+ filename: session.filename
552
+ });
553
+ setStatus(session.can_edit ? "All changes saved" : "Read only \xB7 live");
554
+ }
555
+ void start().catch((e) => {
556
+ if (!cancelled) setError(e instanceof Error ? e.message : String(e));
557
+ });
558
+ return () => {
559
+ cancelled = true;
560
+ cleanups.forEach((f) => f());
561
+ clearTimeout(draftTimer.current);
562
+ fitCleanup.current?.();
563
+ room?.destroy();
564
+ };
565
+ }, [documentId, isDraft, snapshot, retry]);
566
+ useEffect(() => {
567
+ adapter.onDirtyChange?.(dirty);
568
+ const beforeUnload = (e) => {
569
+ if (dirtyRef.current) e.preventDefault();
570
+ };
571
+ window.addEventListener("beforeunload", beforeUnload);
572
+ return () => {
573
+ window.removeEventListener("beforeunload", beforeUnload);
574
+ adapter.onDirtyChange?.(false);
575
+ };
576
+ }, [dirty]);
577
+ async function exportDocument() {
578
+ const data = await ref.current?.save();
579
+ if (!data) return;
580
+ const url = URL.createObjectURL(
581
+ new Blob([normalizeDocxExport(data)], {
582
+ type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
583
+ })
584
+ );
585
+ const a = document.createElement("a");
586
+ a.href = url;
587
+ a.download = title.replace(/\.[^.]+$/, "") + ".docx";
588
+ a.click();
589
+ setTimeout(() => URL.revokeObjectURL(url), 5e3);
590
+ }
591
+ useLayoutEffect(() => {
592
+ const editor = ref.current;
593
+ return () => {
594
+ if (isDraft && editor && dirtyRef.current)
595
+ void editor.save().then((bytes) => {
596
+ if (bytes) drafts.set(documentId, bytes);
597
+ }).catch(() => {
598
+ });
599
+ };
600
+ }, [documentId, isDraft, ready]);
601
+ async function saveVersion() {
602
+ if (!ready || saving) return;
603
+ setSaving(true);
604
+ const atRevision = revision.current;
605
+ try {
606
+ const result = isDraft ? await (async () => {
607
+ const bytes = await ref.current?.save();
608
+ if (!bytes) throw new Error("The editor is not ready.");
609
+ return (await adapter.request(`/notes?${new URLSearchParams({ title })}`, {
610
+ method: "POST",
611
+ headers: {
612
+ "Content-Type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
613
+ },
614
+ body: bytes
615
+ })).json();
616
+ })() : await adapter.save(documentId, ready.room ? encodeState(ready.room) : void 0);
617
+ if (revision.current === atRevision) {
618
+ dirtyRef.current = false;
619
+ setDirty(false);
620
+ drafts.delete(documentId);
621
+ }
622
+ await Promise.resolve(adapter.onVersionSaved?.(result.document_ext_id)).catch(() => {
623
+ });
624
+ onSaved?.(result.document_ext_id);
625
+ adapter.notify?.(isDraft ? "Word note saved" : "DOCX version saved", "success");
626
+ } catch (e) {
627
+ adapter.notify?.(e instanceof Error ? e.message : "Could not save this document.", "error");
628
+ setStatus("Save failed \xB7 your draft is retained");
629
+ } finally {
630
+ setSaving(false);
631
+ }
632
+ }
633
+ function editorReady(editor) {
634
+ ref.current = editor;
635
+ fitCleanup.current?.();
636
+ const viewport = stageRef.current?.querySelector(".native-docx-viewport");
637
+ if (!viewport) return;
638
+ const fit = () => {
639
+ const pageWidth = editor.getPageGeometry()[0]?.box.width;
640
+ if (!pageWidth) return;
641
+ const style = getComputedStyle(viewport);
642
+ const available = viewport.clientWidth - parseFloat(style.paddingLeft) - parseFloat(style.paddingRight);
643
+ const zoom = Math.max(0.1, Math.min(2.5, available / pageWidth));
644
+ if (Math.abs(editor.getZoom() - zoom) > 1e-3) editor.setZoom(zoom);
645
+ };
646
+ const resize = new ResizeObserver(fit);
647
+ const chrome = new MutationObserver(fit);
648
+ resize.observe(viewport);
649
+ chrome.observe(viewport, { attributes: true, attributeFilter: ["style", "data-review-pane"] });
650
+ fit();
651
+ fitCleanup.current = () => {
652
+ resize.disconnect();
653
+ chrome.disconnect();
654
+ };
655
+ }
656
+ function changed() {
657
+ if (snapshot) return;
658
+ revision.current += 1;
659
+ dirtyRef.current = true;
660
+ setDirty(true);
661
+ setStatus(
662
+ isDraft ? "Unsaved note" : offline ? "Offline \xB7 edits buffered locally" : "Saving changes\u2026"
663
+ );
664
+ if (isDraft) {
665
+ clearTimeout(draftTimer.current);
666
+ draftTimer.current = setTimeout(() => {
667
+ void ref.current?.save().then((bytes) => {
668
+ if (bytes) drafts.set(documentId, bytes);
669
+ });
670
+ }, 200);
671
+ }
672
+ }
673
+ return /* @__PURE__ */ jsxs("section", { className: "native-docx-viewer", "data-testid": "native-docx-editor", children: [
674
+ /* @__PURE__ */ jsxs("div", { className: "native-docx-actions", children: [
675
+ /* @__PURE__ */ jsxs("span", { className: "native-docx-status", role: "status", children: [
676
+ offline ? /* @__PURE__ */ jsx(WifiOff, { size: 14 }) : !dirty && ready ? /* @__PURE__ */ jsx(Check, { size: 14 }) : null,
677
+ status
678
+ ] }),
679
+ /* @__PURE__ */ jsx(DocxParticipants, { session: ready?.room?.session }),
680
+ /* @__PURE__ */ jsxs("button", { onClick: () => void exportDocument(), disabled: !ready, "aria-label": "Download DOCX", children: [
681
+ /* @__PURE__ */ jsx(Download, { size: 14 }),
682
+ "DOCX"
683
+ ] }),
684
+ !snapshot && ready?.canEdit && /* @__PURE__ */ jsxs(
685
+ "button",
686
+ {
687
+ className: "native-docx-save",
688
+ onClick: () => void saveVersion(),
689
+ disabled: !ready || saving || offline,
690
+ "data-testid": "native-docx-save",
691
+ children: [
692
+ /* @__PURE__ */ jsx(Save, { size: 14 }),
693
+ saving ? "Saving\u2026" : isDraft ? "Save note" : "Save version"
694
+ ]
695
+ }
696
+ )
697
+ ] }),
698
+ offline && /* @__PURE__ */ jsx("div", { className: "native-docx-notice", children: "Changes are buffered in this browser. Keep this tab open until the connection returns, or download your DOCX." }),
699
+ error && /* @__PURE__ */ jsxs("div", { className: "native-docx-notice", "data-error": "true", role: "alert", children: [
700
+ error,
701
+ " ",
702
+ /* @__PURE__ */ jsxs(
703
+ "button",
704
+ {
705
+ disabled: dirty,
706
+ title: dirty ? "Download your unsaved DOCX before reopening the editor" : void 0,
707
+ onClick: () => setRetry((x) => x + 1),
708
+ children: [
709
+ /* @__PURE__ */ jsx(RefreshCw, { size: 12 }),
710
+ "Retry"
711
+ ]
712
+ }
713
+ )
714
+ ] }),
715
+ !ready && !error && /* @__PURE__ */ jsx("div", { className: "native-docx-error", role: "status", children: "Opening Word document\u2026" }),
716
+ ready && /* @__PURE__ */ jsx("div", { className: "native-docx-body docx-editor", children: /* @__PURE__ */ jsx(EditorBoundary, { children: /* @__PURE__ */ jsxs(
717
+ DocxEditor.Root,
718
+ {
719
+ document: ready.document,
720
+ modules: ready.modules,
721
+ fonts: ready.fonts,
722
+ mode: !ready.canEdit || snapshot ? "view" : "suggesting",
723
+ reviewDisplayMode: "all-markup",
724
+ zoomMode: fixedZoomMode,
725
+ author: ready.author,
726
+ onReady: editorReady,
727
+ onChange: (change) => {
728
+ if (change.source !== "load") changed();
729
+ },
730
+ onFontError: (e) => setError(e.message),
731
+ children: [
732
+ /* @__PURE__ */ jsx(
733
+ DocxEditor.RevisionMarkup,
734
+ {
735
+ insertions: { mark: "underline", color: "blue" },
736
+ deletions: { mark: "strikethrough", color: "red" },
737
+ movedFrom: { mark: "doubleStrikethrough", color: "green" },
738
+ movedTo: { mark: "doubleUnderline", color: "green" },
739
+ trackMoves: true
740
+ }
741
+ ),
742
+ /* @__PURE__ */ jsx(DocxRibbon, { onSave: () => void saveVersion() }),
743
+ /* @__PURE__ */ jsxs(
744
+ "div",
745
+ {
746
+ className: "native-docx-stage",
747
+ ref: stageRef,
748
+ onMouseDownCapture: (event) => {
749
+ if (event.button === 2 && ref.current?.getSelectionPlacement())
750
+ event.preventDefault();
751
+ },
752
+ children: [
753
+ /* @__PURE__ */ jsxs(DocxEditor.Viewport, { className: "native-docx-viewport", children: [
754
+ /* @__PURE__ */ jsx(DocxEditor.Content, {}),
755
+ /* @__PURE__ */ jsx(
756
+ DocxMentions,
757
+ {
758
+ initialCommentId,
759
+ mentions: adapter.mentions,
760
+ notify: adapter.notify,
761
+ requestAgent: adapter.requestAgent ? (request) => adapter.requestAgent(documentId, request) : void 0,
762
+ children: /* @__PURE__ */ jsx(DocxEditorReview, {})
763
+ }
764
+ ),
765
+ /* @__PURE__ */ jsx(DocxEditor.HeaderFooterChrome, {}),
766
+ /* @__PURE__ */ jsx(DocxEditor.NotesChrome, {}),
767
+ /* @__PURE__ */ jsx(DocxEditor.Equation, {}),
768
+ /* @__PURE__ */ jsx(DocxEditor.HyperLink, {}),
769
+ /* @__PURE__ */ jsx(DocxEditor.ContextMenu, {})
770
+ ] }),
771
+ /* @__PURE__ */ jsx(DocxEditor.PageNumber, {})
772
+ ]
773
+ }
774
+ )
775
+ ]
776
+ }
777
+ ) }, retry) })
778
+ ] });
779
+ }
780
+ var loaded = /* @__PURE__ */ new Map();
781
+ function loadDocxFonts(baseUrl = "/native-docx/fonts") {
782
+ let promise = loaded.get(baseUrl);
783
+ if (!promise) {
784
+ promise = loadFonts(baseUrl).catch((error) => {
785
+ loaded.delete(baseUrl);
786
+ throw error;
787
+ });
788
+ loaded.set(baseUrl, promise);
789
+ }
790
+ return promise;
791
+ }
792
+ async function loadFonts(baseUrl) {
793
+ const families = ["Liberation Serif", "Liberation Sans", "Liberation Mono"];
794
+ const variants = [
795
+ { suffix: "Regular", weight: 400, style: "normal" },
796
+ { suffix: "Bold", weight: 700, style: "normal" },
797
+ { suffix: "Italic", weight: 400, style: "italic" },
798
+ { suffix: "BoldItalic", weight: 700, style: "italic" }
799
+ ];
800
+ const sources = await Promise.all(
801
+ families.flatMap(
802
+ (family) => variants.map(async (face) => {
803
+ const r = await fetch(
804
+ `${baseUrl.replace(/\/$/, "")}/${family.replace(" ", "")}-${face.suffix}.ttf`
805
+ );
806
+ if (!r.ok) throw new Error(`Could not load the document font ${family}.`);
807
+ const result = createFontSource(new Uint8Array(await r.arrayBuffer()), {
808
+ family,
809
+ weight: face.weight,
810
+ style: face.style
811
+ });
812
+ if ("failure" in result)
813
+ throw new Error(`Could not initialize the document font ${family}.`);
814
+ return result.source;
815
+ })
816
+ )
817
+ );
818
+ const aliases = {
819
+ "Times New Roman": "Liberation Serif",
820
+ Cambria: "Liberation Serif",
821
+ "DejaVu Serif": "Liberation Serif",
822
+ Calibri: "Liberation Sans",
823
+ Aptos: "Liberation Sans",
824
+ Arial: "Liberation Sans",
825
+ Helvetica: "Liberation Sans",
826
+ "Courier New": "Liberation Mono",
827
+ "DejaVu Sans Mono": "Liberation Mono"
828
+ };
829
+ return {
830
+ epoch: 1,
831
+ maxFontBytes: 12 * 1024 * 1024,
832
+ sources,
833
+ defaultFont: { family: "Liberation Serif", sizeHalfPoints: 22 },
834
+ supportedFamilies: [...families, ...Object.keys(aliases)],
835
+ substitutions: Object.entries(aliases).flatMap(
836
+ ([from, to]) => variants.map((face) => ({
837
+ from: { family: from, weight: face.weight, style: face.style },
838
+ to: { family: to, weight: face.weight, style: face.style }
839
+ }))
840
+ )
841
+ };
842
+ }
843
+
844
+ export { DocxMentions, DocxParticipants, DocxRibbon, NativeDocxEditor, loadDocxFonts, normalizeDocxExport };
845
+ //# sourceMappingURL=chunk-UOLMFC5K.js.map
846
+ //# sourceMappingURL=chunk-UOLMFC5K.js.map