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