@w100n/documents 0.3.0 → 0.10.0

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 (55) hide show
  1. package/dist/SheetView-3XTEV2VT.css +319 -0
  2. package/dist/SheetView-3XTEV2VT.css.map +1 -0
  3. package/dist/SheetView-JYH2ZUZF.js +928 -0
  4. package/dist/SheetView-JYH2ZUZF.js.map +1 -0
  5. package/dist/TextEditor-X3X62R46.js +982 -0
  6. package/dist/TextEditor-X3X62R46.js.map +1 -0
  7. package/dist/caveat-cyrillic-XVO43JCM.woff2 +0 -0
  8. package/dist/caveat-latin-IUBKKMHQ.woff2 +0 -0
  9. package/dist/caveat-latin-ext-5S7R5SZN.woff2 +0 -0
  10. package/dist/chunk-CE3AADY6.js +3447 -0
  11. package/dist/chunk-CE3AADY6.js.map +1 -0
  12. package/dist/chunk-ISOOUYLI.js +150 -0
  13. package/dist/chunk-ISOOUYLI.js.map +1 -0
  14. package/dist/{chunk-KEV4R7W5.js → chunk-K364NAYP.js} +4 -1
  15. package/dist/chunk-K364NAYP.js.map +1 -0
  16. package/dist/chunk-QDH7EZAF.js +27 -0
  17. package/dist/chunk-QDH7EZAF.js.map +1 -0
  18. package/dist/chunk-SVRYWMES.js +196 -0
  19. package/dist/chunk-SVRYWMES.js.map +1 -0
  20. package/dist/chunk-TDP6OJET.js +31 -0
  21. package/dist/chunk-TDP6OJET.js.map +1 -0
  22. package/dist/chunk-TNSN4ZQ5.js +2909 -0
  23. package/dist/chunk-TNSN4ZQ5.js.map +1 -0
  24. package/dist/collab.cjs +6312 -0
  25. package/dist/collab.cjs.map +1 -0
  26. package/dist/collab.d.cts +39 -0
  27. package/dist/collab.d.ts +39 -0
  28. package/dist/collab.js +30 -0
  29. package/dist/collab.js.map +1 -0
  30. package/dist/grid-EI5JNZAA.js +88 -0
  31. package/dist/grid-EI5JNZAA.js.map +1 -0
  32. package/dist/index.cjs +12265 -407
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.css +3448 -214
  35. package/dist/index.css.map +1 -1
  36. package/dist/index.d.cts +145 -7
  37. package/dist/index.d.ts +145 -7
  38. package/dist/index.js +3469 -340
  39. package/dist/index.js.map +1 -1
  40. package/dist/memory.cjs +59 -7
  41. package/dist/memory.cjs.map +1 -1
  42. package/dist/memory.d.cts +7 -2
  43. package/dist/memory.d.ts +7 -2
  44. package/dist/memory.js +57 -8
  45. package/dist/memory.js.map +1 -1
  46. package/dist/pdfWrite-EM6GJDBF.js +76 -0
  47. package/dist/pdfWrite-EM6GJDBF.js.map +1 -0
  48. package/dist/types-B5oF25dn.d.cts +165 -0
  49. package/dist/types-B5oF25dn.d.ts +165 -0
  50. package/dist/write-DaVtTJCS.d.cts +6 -0
  51. package/dist/write-DaVtTJCS.d.ts +6 -0
  52. package/package.json +45 -3
  53. package/dist/chunk-KEV4R7W5.js.map +0 -1
  54. package/dist/types-DiEpNWSu.d.cts +0 -59
  55. package/dist/types-DiEpNWSu.d.ts +0 -59
@@ -0,0 +1,982 @@
1
+ 'use client';
2
+ import {
3
+ useLoad
4
+ } from "./chunk-QDH7EZAF.js";
5
+ import {
6
+ useLive
7
+ } from "./chunk-SVRYWMES.js";
8
+ import {
9
+ a4,
10
+ align,
11
+ changes,
12
+ clear,
13
+ decide,
14
+ entries,
15
+ fromMarkdown,
16
+ indent,
17
+ kit,
18
+ letter,
19
+ locate,
20
+ notes,
21
+ paging,
22
+ suggesting,
23
+ toMarkdown,
24
+ track
25
+ } from "./chunk-CE3AADY6.js";
26
+ import {
27
+ rove,
28
+ useRestore
29
+ } from "./chunk-TDP6OJET.js";
30
+ import {
31
+ readDocx,
32
+ writeDocx
33
+ } from "./chunk-TNSN4ZQ5.js";
34
+ import "./chunk-ISOOUYLI.js";
35
+
36
+ // src/editor/TextEditor.tsx
37
+ import { EditorContent, useEditor, useEditorState } from "@tiptap/react";
38
+ import {
39
+ AlignCenter,
40
+ AlignJustify,
41
+ AlignLeft,
42
+ AlignRight,
43
+ Baseline,
44
+ BetweenHorizontalEnd,
45
+ BetweenHorizontalStart,
46
+ BetweenVerticalEnd,
47
+ BetweenVerticalStart,
48
+ Bold,
49
+ Check,
50
+ CheckCheck,
51
+ ChevronDown,
52
+ Code,
53
+ FilePenLine,
54
+ Highlighter,
55
+ ImagePlus,
56
+ Italic,
57
+ Link,
58
+ Link2Off,
59
+ List,
60
+ ListChecks,
61
+ ListIndentDecrease,
62
+ ListIndentIncrease,
63
+ ListOrdered,
64
+ ListTree,
65
+ Maximize2,
66
+ MessageSquarePlus,
67
+ MessagesSquare,
68
+ Minus,
69
+ MoveHorizontal,
70
+ PanelTop,
71
+ Pencil,
72
+ Plus,
73
+ Redo2,
74
+ RemoveFormatting,
75
+ Reply,
76
+ RotateCcw,
77
+ Save,
78
+ SeparatorHorizontal,
79
+ Strikethrough,
80
+ Table,
81
+ TableCellsMerge,
82
+ TableColumnsSplit,
83
+ TableOfContents,
84
+ TableRowsSplit,
85
+ Trash2,
86
+ Underline,
87
+ Undo2,
88
+ WifiOff,
89
+ X
90
+ } from "lucide-react";
91
+ import { Fragment as Fragment2, useEffect, useRef as useRef2, useState as useState2 } from "react";
92
+ import { createPortal } from "react-dom";
93
+
94
+ // src/viewers/DocxView.tsx
95
+ import { useLayoutEffect, useRef, useState } from "react";
96
+
97
+ // src/viewers/headings.ts
98
+ var last = 0;
99
+ function collect(root, selector, level = (el) => Number(el.tagName.slice(1))) {
100
+ return [...root.querySelectorAll(selector)].flatMap((el) => {
101
+ const text = el.textContent?.trim() ?? "";
102
+ if (!text) return [];
103
+ if (!el.id) el.id = `w100n-docs-h${++last}`;
104
+ return [{ id: el.id, level: Math.min(3, Math.max(1, level(el) || 1)), text }];
105
+ });
106
+ }
107
+
108
+ // src/viewers/DocxView.tsx
109
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
110
+ function DocxView({ bytes, onOutline, failed }) {
111
+ const box = useRef(null);
112
+ const [error, setError] = useState(false);
113
+ useLayoutEffect(() => {
114
+ let live = true;
115
+ let width = 0;
116
+ const root = box.current;
117
+ const fit = () => {
118
+ const wrapper = root.querySelector(".w100n-docx-wrapper");
119
+ if (wrapper && width) wrapper.style.zoom = String(Math.min(1, root.clientWidth / width));
120
+ };
121
+ const watch = new ResizeObserver(fit);
122
+ root.replaceChildren();
123
+ setError(false);
124
+ import("docx-preview").then(({ renderAsync }) => renderAsync(bytes, root, void 0, { inWrapper: true, className: "w100n-docx", ignoreLastRenderedPageBreak: true })).then(() => {
125
+ if (!live) return;
126
+ width = Math.max(0, ...[...root.querySelectorAll("section.w100n-docx")].map((page) => page.offsetWidth));
127
+ fit();
128
+ watch.observe(root);
129
+ onOutline(collect(root, '[class*="heading"]', (el) => Number(/heading(\d)/i.exec(el.className)?.[1] ?? 1)));
130
+ }).catch(() => {
131
+ if (live) setError(true);
132
+ });
133
+ return () => {
134
+ live = false;
135
+ watch.disconnect();
136
+ };
137
+ }, [bytes, onOutline]);
138
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
139
+ error && /* @__PURE__ */ jsx("p", { className: "w100n-docs-note", children: failed }),
140
+ /* @__PURE__ */ jsx("div", { ref: box, className: "w100n-docs-docx" })
141
+ ] });
142
+ }
143
+
144
+ // src/editor/TextEditor.tsx
145
+ import { Fragment as Fragment3, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
146
+ var inks = ["#0f172a", "#475569", "#dc2626", "#ea580c", "#ca8a04", "#16a34a", "#0891b2", "#2563eb", "#7c3aed", "#db2777"];
147
+ var glows = ["#fef08a", "#bbf7d0", "#bfdbfe", "#fbcfe8", "#fed7aa", "#ddd6fe"];
148
+ var uid = () => Math.random().toString(36).slice(2, 10);
149
+ function TextEditor({ kind, bytes, t, language, rights, host, together, documentId, onSaved, onOutline, onPanel, slot, guard }) {
150
+ const docx = kind === "docx";
151
+ const can = { edit: !!host?.save && !!rights?.edit, comment: !!host?.comment && !!rights && (rights.comment || rights.edit) };
152
+ const source = useLoad(async () => {
153
+ if (docx) return { ...await readDocx(bytes), md: null };
154
+ const md = fromMarkdown(new TextDecoder().decode(bytes));
155
+ return { doc: md.doc, md, styles: [], page: a4 };
156
+ }, [bytes, kind]);
157
+ const live = useLive(together ? host : void 0, documentId, kind, bytes, source.data?.doc, language, can.edit, onSaved);
158
+ const shared = live?.ready ? live.wire : null;
159
+ if (live?.readonly) can.edit = false;
160
+ const baseline = useRef2(null);
161
+ const anchors = useRef2(/* @__PURE__ */ new Map());
162
+ const scroller = useRef2(null);
163
+ const [tab, setTab] = useState2(can.edit ? "home" : can.comment ? "review" : "view");
164
+ const [pop, setPop] = useState2(null);
165
+ const [zoom, setZoom] = useState2(null);
166
+ const [layout, setLayout] = useState2(null);
167
+ const [room, setRoom] = useState2(0);
168
+ const [saving, setSaving] = useState2("idle");
169
+ const [href, setHref] = useState2("");
170
+ const [grid, setGrid] = useState2({ rows: 0, cols: 0 });
171
+ const [draft, setDraft] = useState2(null);
172
+ const [reply, setReply] = useState2(null);
173
+ const [active, setActive] = useState2(null);
174
+ const [resolved, setResolved] = useState2(false);
175
+ const [posting, setPosting] = useState2("idle");
176
+ const [added, setAdded] = useState2([]);
177
+ const [changed, setChanged] = useState2({});
178
+ const [editing, setEditing] = useState2(null);
179
+ const [dropping, setDropping] = useState2(null);
180
+ const [failed, setFailed] = useState2(false);
181
+ useRestore(pop !== null);
182
+ useRestore(editing !== null);
183
+ useRestore(dropping !== null);
184
+ const editor = useEditor({
185
+ extensions: kit(kind, t, shared),
186
+ content: shared ? null : source.data?.doc ?? null,
187
+ editable: can.edit && (!live || !!shared),
188
+ immediatelyRender: true,
189
+ shouldRerenderOnTransaction: false,
190
+ editorProps: { attributes: { class: "w100n-docs-prose w100n-text-body", "aria-label": t.documentText } },
191
+ onCreate: ({ editor: made }) => {
192
+ if (source.data && !live) baseline.current = { bytes, doc: made.state.doc, base: source.data.md && track(source.data.md, made.state.doc) };
193
+ }
194
+ }, [source.data, can.edit, shared]);
195
+ const state = useEditorState({
196
+ editor,
197
+ selector: ({ editor: e }) => {
198
+ if (!e) return null;
199
+ const { $from, empty } = e.state.selection;
200
+ const block = $from.parent;
201
+ return {
202
+ style: e.isActive("codeBlock") ? "code" : block.type.name === "heading" ? `h${block.attrs.level}` : e.isActive("blockquote") ? "quote" : "normal",
203
+ bold: e.isActive("bold"),
204
+ italic: e.isActive("italic"),
205
+ underline: e.isActive("underline"),
206
+ strike: e.isActive("strike"),
207
+ code: e.isActive("code"),
208
+ bullets: e.isActive("bulletList"),
209
+ numbers: e.isActive("orderedList"),
210
+ tasks: e.isActive("taskList"),
211
+ align: block.attrs.align ?? "left",
212
+ table: e.isActive("table"),
213
+ link: e.isActive("link"),
214
+ color: e.getAttributes("textStyle").color ?? null,
215
+ glow: e.isActive("highlight"),
216
+ undo: e.can().undo(),
217
+ redo: e.can().redo(),
218
+ empty,
219
+ toc: (() => {
220
+ let at = -1;
221
+ e.state.doc.forEach((node, pos) => {
222
+ if (at < 0 && node.type.name === "toc") at = pos;
223
+ });
224
+ return at;
225
+ })(),
226
+ suggest: !!suggesting.getState(e.state)?.on,
227
+ revs: docx ? changes(e.state.doc) : [],
228
+ dirty: !!baseline.current && !e.state.doc.eq(baseline.current.doc)
229
+ };
230
+ }
231
+ });
232
+ const page = layout ?? source.data?.page ?? a4;
233
+ useEffect(() => {
234
+ if (editor && docx && source.data) editor.view.dispatch(editor.state.tr.setMeta(paging, { page }));
235
+ }, [editor, source.data, page]);
236
+ const loaded = useLoad((signal) => host?.comments ? host.comments(documentId, { signal }) : Promise.resolve([]), [host, documentId]);
237
+ const all = [...loaded.data ?? [], ...added].flatMap((c) => c.id in changed ? changed[c.id] ?? [] : [c]).filter((c) => !c.replyTo || changed[c.replyTo] !== null);
238
+ const open = all.filter((c) => !c.replyTo && !c.resolved);
239
+ const threads = all.filter((c) => !c.replyTo && (resolved || !c.resolved)).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
240
+ const openIds = open.map((c) => c.id).join();
241
+ useEffect(() => {
242
+ if (!editor || !source.data) return;
243
+ const have = new Map(notes.getState(editor.state).spots.map((s) => [s.id, s]));
244
+ const fresh = open.filter((c) => !have.has(c.id));
245
+ const found = locate(editor.state.doc, fresh.map((c) => ({ id: c.id, quote: c.quote, anchor: anchors.current.get(c.id) })));
246
+ editor.view.dispatch(editor.state.tr.setMeta(notes, { spots: [...open.flatMap((c) => have.get(c.id) ?? []), ...found] }));
247
+ }, [editor, source.data, openIds]);
248
+ useEffect(() => {
249
+ if (editor && source.data) editor.view.dispatch(editor.state.tr.setMeta(notes, { active }));
250
+ }, [editor, source.data, active]);
251
+ useEffect(() => {
252
+ if (!editor || !source.data) return;
253
+ let timer = 0;
254
+ let last2 = "";
255
+ const run = () => {
256
+ const list = [];
257
+ editor.state.doc.forEach((node, offset) => {
258
+ if (node.type.name !== "heading" || !node.textContent.trim()) return;
259
+ list.push({ id: `h${offset}`, level: Math.min(3, node.attrs.level), text: node.textContent.trim(), go: () => {
260
+ editor.view.nodeDOM(offset)?.scrollIntoView({ behavior: "smooth", block: "start" });
261
+ editor.commands.setTextSelection(offset + 1);
262
+ } });
263
+ });
264
+ const key = JSON.stringify(list.map((h) => [h.id, h.level, h.text]));
265
+ if (key === last2) return;
266
+ last2 = key;
267
+ onOutline(list);
268
+ };
269
+ const later = () => {
270
+ clearTimeout(timer);
271
+ timer = window.setTimeout(run, 300);
272
+ };
273
+ run();
274
+ editor.on("update", later);
275
+ return () => {
276
+ clearTimeout(timer);
277
+ editor.off("update", later);
278
+ };
279
+ }, [editor, source.data, onOutline]);
280
+ const dirty = live ? live.pending : !!state?.dirty;
281
+ const unsent = live ? live.unsynced && can.edit : dirty;
282
+ useEffect(() => {
283
+ if (!guard || live || !dirty || !source.data || !editor) return;
284
+ guard.current = save;
285
+ return () => {
286
+ guard.current = null;
287
+ };
288
+ });
289
+ useEffect(() => {
290
+ if (!unsent) return;
291
+ const stop = (e) => e.preventDefault();
292
+ window.addEventListener("beforeunload", stop);
293
+ return () => window.removeEventListener("beforeunload", stop);
294
+ }, [unsent]);
295
+ useEffect(() => {
296
+ const el = scroller.current;
297
+ if (!el || typeof ResizeObserver === "undefined") return;
298
+ const watch = new ResizeObserver(() => setRoom(el.clientWidth));
299
+ watch.observe(el);
300
+ return () => watch.disconnect();
301
+ }, [editor, source.data]);
302
+ useEffect(() => {
303
+ if (!pop) return;
304
+ const close = (e) => {
305
+ if (!e.target.closest(".w100n-text-pop, [data-pop]")) setPop(null);
306
+ };
307
+ document.addEventListener("pointerdown", close);
308
+ return () => document.removeEventListener("pointerdown", close);
309
+ }, [pop]);
310
+ if (source.error) return docx ? /* @__PURE__ */ jsx2("div", { className: "w100n-docs-scroll", children: /* @__PURE__ */ jsx2(DocxView, { bytes, onOutline, failed: t.failed }) }) : /* @__PURE__ */ jsx2("div", { className: "w100n-docs-empty", children: t.failed });
311
+ if (!source.data || !editor || !state || live && !live.ready && !live.late && !live.denied) return /* @__PURE__ */ jsx2("div", { className: "w100n-docs-empty", children: t.loading });
312
+ const save = async () => {
313
+ if (live) {
314
+ if (!can.edit || !live.ready || !live.online || !live.pending || saving === "busy") return false;
315
+ setSaving("busy");
316
+ const ok = await live.save();
317
+ setSaving(ok ? "done" : "failed");
318
+ return ok;
319
+ }
320
+ const was = baseline.current;
321
+ const doc = editor.state.doc;
322
+ if (!can.edit || !was || saving === "busy" || doc.eq(was.doc)) return false;
323
+ setSaving("busy");
324
+ try {
325
+ const md = was.base && toMarkdown(doc, was.base);
326
+ const out = md ? new TextEncoder().encode(md.text).buffer : await writeDocx(was.bytes, editor.getJSON());
327
+ await host.save(documentId, out, t.editedNote);
328
+ baseline.current = { bytes: md ? out : was.bytes, doc, base: md ? md.base : null };
329
+ if (!md && editor.state.doc === doc) {
330
+ const fresh = (await readDocx(out)).doc;
331
+ if (editor.state.doc === doc && renew(fresh)) baseline.current = { bytes: out, doc: editor.state.doc, base: null };
332
+ }
333
+ setSaving("done");
334
+ onSaved?.();
335
+ return true;
336
+ } catch {
337
+ setSaving("failed");
338
+ return false;
339
+ }
340
+ };
341
+ const renew = (fresh) => {
342
+ const at = [];
343
+ const refs = [];
344
+ editor.state.doc.descendants((node, pos) => {
345
+ if (node.isBlock) at.push(pos);
346
+ });
347
+ editor.schema.nodeFromJSON(fresh).descendants((node) => {
348
+ if (node.isBlock) refs.push(node.type.name, node.attrs.ref);
349
+ });
350
+ if (refs.length !== at.length * 2 || at.some((pos, i) => editor.state.doc.nodeAt(pos).type.name !== refs[i * 2])) return false;
351
+ const tr = editor.state.tr.setMeta("addToHistory", false);
352
+ at.forEach((pos, i) => {
353
+ if (tr.doc.nodeAt(pos).attrs.ref !== refs[i * 2 + 1]) tr.setNodeAttribute(pos, "ref", refs[i * 2 + 1]);
354
+ });
355
+ editor.view.dispatch(tr);
356
+ return true;
357
+ };
358
+ const linkUp = () => {
359
+ setHref(editor.getAttributes("link").href ?? "");
360
+ setTab("insert");
361
+ setPop("link");
362
+ };
363
+ const putLink = () => {
364
+ const value = href.trim();
365
+ const chain = editor.chain().focus();
366
+ if (!value) chain.extendMarkRange("link").unsetLink().run();
367
+ else if (editor.state.selection.empty && !editor.isActive("link")) chain.insertContent({ type: "text", text: value, marks: [{ type: "link", attrs: { href: value } }] }).run();
368
+ else chain.extendMarkRange("link").setLink({ href: value }).run();
369
+ setPop(null);
370
+ };
371
+ const begin = () => {
372
+ const { from, to, empty } = editor.state.selection;
373
+ if (empty || !can.comment) return;
374
+ editor.view.dispatch(editor.state.tr.setMeta(notes, { pending: { from, to } }));
375
+ setDraft({ quote: editor.state.doc.textBetween(from, to, "\n", " "), body: "" });
376
+ setPosting("idle");
377
+ onPanel?.("comments");
378
+ };
379
+ const drop = () => {
380
+ editor.view.dispatch(editor.state.tr.setMeta(notes, { pending: null }));
381
+ setDraft(null);
382
+ };
383
+ const post = async () => {
384
+ if (!draft?.body.trim() || !host?.comment || posting === "busy") return;
385
+ setPosting("busy");
386
+ try {
387
+ const anchor = uid();
388
+ const made = await host.comment(documentId, { body: draft.body.trim(), quote: draft.quote, replyTo: null, anchor });
389
+ anchors.current.set(made.id, anchor);
390
+ const range = notes.getState(editor.state).pending;
391
+ editor.view.dispatch(editor.state.tr.setMeta(notes, { pending: null, spots: [...notes.getState(editor.state).spots, ...range ? [{ id: made.id, ...range }] : []] }));
392
+ setAdded((list) => [...list, made]);
393
+ setDraft(null);
394
+ setActive(made.id);
395
+ setPosting("idle");
396
+ } catch {
397
+ setPosting("failed");
398
+ }
399
+ };
400
+ const answer = async (parent) => {
401
+ if (!reply?.body.trim() || !host?.comment || posting === "busy") return;
402
+ setPosting("busy");
403
+ try {
404
+ const made = await host.comment(documentId, { body: reply.body.trim(), quote: parent.quote, replyTo: parent.id, anchor: anchors.current.get(parent.id) ?? "" });
405
+ setAdded((list) => [...list, made]);
406
+ setReply(null);
407
+ setPosting("idle");
408
+ } catch {
409
+ setPosting("failed");
410
+ }
411
+ };
412
+ const settle = async (c) => {
413
+ try {
414
+ await host.resolveComment(documentId, c.id, !c.resolved);
415
+ setChanged((list) => ({ ...list, [c.id]: { ...c, resolved: !c.resolved } }));
416
+ } catch {
417
+ setPosting("failed");
418
+ }
419
+ };
420
+ const change = async (id, body) => {
421
+ if (posting === "busy") return;
422
+ setPosting("busy");
423
+ setFailed(false);
424
+ try {
425
+ let made = null;
426
+ if (body === null) await host.deleteComment(documentId, id);
427
+ else made = await host.editComment(documentId, id, body);
428
+ setChanged((list) => ({ ...list, [id]: made }));
429
+ setEditing(null);
430
+ setDropping(null);
431
+ if (body === null && active === id) setActive(null);
432
+ } catch {
433
+ setFailed(true);
434
+ }
435
+ setPosting("idle");
436
+ };
437
+ const mine = (c) => c.author.id === host?.me.id;
438
+ const show = (c) => {
439
+ setActive(c.id);
440
+ const spot = notes.getState(editor.state).spots.find((s) => s.id === c.id);
441
+ if (!spot) return;
442
+ const at = editor.view.domAtPos(spot.from).node;
443
+ (at instanceof HTMLElement ? at : at.parentElement)?.scrollIntoView({ behavior: "smooth", block: "center" });
444
+ };
445
+ const suggested = [...state.revs.reduce((all2, c) => {
446
+ const key = `${c.kind}:${c.id}`;
447
+ const was = all2.get(key);
448
+ all2.set(key, was ? { ...was, text: `${was.text} ${c.text}` } : c);
449
+ return all2;
450
+ }, /* @__PURE__ */ new Map()).values()];
451
+ const jump = (at) => {
452
+ const dom = editor.view.domAtPos(at).node;
453
+ (dom instanceof HTMLElement ? dom : dom.parentElement)?.scrollIntoView({ behavior: "smooth", block: "center" });
454
+ };
455
+ const percent = zoom ?? (docx && room ? Math.max(10, Math.min(100, Math.floor((room - 24) / page.width * 100))) : 100);
456
+ const zoomBy = (d) => setZoom(Math.min(200, Math.max(30, percent + d)));
457
+ const keys = (e) => {
458
+ if (e.defaultPrevented) return;
459
+ const key = e.key.toLowerCase();
460
+ const mod = e.metaKey || e.ctrlKey;
461
+ if (key === "escape" && (pop || draft)) {
462
+ e.stopPropagation();
463
+ if (pop) setPop(null);
464
+ else drop();
465
+ return;
466
+ }
467
+ if (!mod) return;
468
+ const act = (run) => {
469
+ e.preventDefault();
470
+ run();
471
+ };
472
+ if (key === "s" && can.edit) act(() => void save());
473
+ else if (key === "k" && can.edit) act(linkUp);
474
+ else if (key === "m" && e.altKey && can.comment) act(begin);
475
+ else if (!can.edit || e.altKey) return;
476
+ else if (key === "]" || key === "[") act(() => indent(editor, key === "]" ? 1 : -1));
477
+ else if (key === "\\") act(() => clear(editor));
478
+ else if (key === "enter" && docx) act(() => editor.chain().focus().insertContent({ type: "pageBreak" }).run());
479
+ else if (e.shiftKey && docx && ["l", "e", "r", "j"].includes(key)) act(() => align(editor, { l: "left", e: "center", r: "right", j: "justify" }[key]));
480
+ };
481
+ const tool = (icon, label, run, pressed, disabled) => {
482
+ const Icon = icon;
483
+ return /* @__PURE__ */ jsx2("button", { type: "button", "aria-label": label, title: label, "aria-pressed": pressed, disabled, onClick: () => {
484
+ run();
485
+ }, children: /* @__PURE__ */ jsx2(Icon, { "aria-hidden": true }) });
486
+ };
487
+ const gap = /* @__PURE__ */ jsx2("span", { className: "w100n-pdf-gap" });
488
+ const known = source.data.styles;
489
+ const named = (test) => {
490
+ const name = known.find(test)?.name;
491
+ return name && `${name[0].toUpperCase()}${name.slice(1)}`;
492
+ };
493
+ const levels = docx ? [1, 2, 3, 4, 5, 6].filter((level) => level <= 4 || named((s) => s.heading === level)) : [1, 2, 3, 4];
494
+ const styles = [
495
+ ["normal", t.normal, () => editor.chain().focus().clearNodes().setParagraph().run()],
496
+ ...levels.map((level) => [
497
+ `h${level}`,
498
+ named((s) => s.heading === level && !/^title$/i.test(s.name)) || `${t.heading} ${level}`,
499
+ () => editor.chain().focus().setHeading({ level }).run()
500
+ ]),
501
+ ["quote", named((s) => /quote/i.test(s.name)) || t.quote, () => editor.chain().focus().clearNodes().setParagraph().setBlockquote().run()],
502
+ ["code", t.code, () => editor.chain().focus().setCodeBlock().run()]
503
+ ];
504
+ const tabs = [
505
+ ...can.edit ? [["home", t.home], ["insert", t.insert]] : [],
506
+ ...can.edit && docx ? [["layout", t.layout]] : [],
507
+ ...can.edit ? [["refs", t.references]] : [],
508
+ ...host?.comments && (can.comment || open.length > 0) ? [["review", t.review]] : [],
509
+ ["view", t.viewTab]
510
+ ];
511
+ const shown = tabs.some(([key]) => key === tab) ? tab : "view";
512
+ const margins = [[t.marginsNormal, [96, 96, 96, 96]], [t.marginsNarrow, [48, 48, 48, 48]], [t.marginsWide, [96, 192, 96, 192]]];
513
+ const when = (iso) => new Intl.DateTimeFormat(language, { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" }).format(new Date(iso));
514
+ const person = (c) => /* @__PURE__ */ jsxs2("header", { children: [
515
+ c.author.avatarUrl ? /* @__PURE__ */ jsx2("img", { className: "w100n-text-face", src: c.author.avatarUrl, alt: "" }) : /* @__PURE__ */ jsx2("span", { className: "w100n-text-face", "aria-hidden": true, children: c.author.name.slice(0, 1).toUpperCase() }),
516
+ /* @__PURE__ */ jsxs2("span", { className: "w100n-text-who", children: [
517
+ /* @__PURE__ */ jsxs2("b", { children: [
518
+ c.author.name,
519
+ c.author.id.startsWith("guest:") && /* @__PURE__ */ jsx2("span", { className: "w100n-docs-badge", children: t.guest })
520
+ ] }),
521
+ /* @__PURE__ */ jsx2("small", { children: when(c.createdAt) })
522
+ ] }),
523
+ mine(c) && /* @__PURE__ */ jsxs2("span", { className: "w100n-text-own", onClick: (e) => e.stopPropagation(), children: [
524
+ host.editComment && /* @__PURE__ */ jsx2("button", { type: "button", "aria-label": t.editComment, title: t.editComment, onClick: () => {
525
+ setDropping(null);
526
+ setEditing({ id: c.id, body: c.body });
527
+ }, children: /* @__PURE__ */ jsx2(Pencil, { "aria-hidden": true }) }),
528
+ host.deleteComment && /* @__PURE__ */ jsx2("button", { type: "button", "data-tone": "red", "aria-label": t.delete, title: t.delete, onClick: () => {
529
+ setEditing(null);
530
+ setDropping(c.id);
531
+ }, children: /* @__PURE__ */ jsx2(Trash2, { "aria-hidden": true }) })
532
+ ] })
533
+ ] });
534
+ const words = (c) => editing?.id === c.id ? /* @__PURE__ */ jsxs2("div", { className: "w100n-text-reply w100n-text-edit", onClick: (e) => e.stopPropagation(), children: [
535
+ /* @__PURE__ */ jsx2(
536
+ "textarea",
537
+ {
538
+ autoFocus: true,
539
+ value: editing.body,
540
+ "aria-label": t.editComment,
541
+ onChange: (e) => setEditing({ ...editing, body: e.target.value }),
542
+ onKeyDown: (e) => {
543
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey) && editing.body.trim()) void change(c.id, editing.body.trim());
544
+ }
545
+ }
546
+ ),
547
+ /* @__PURE__ */ jsxs2("span", { className: "w100n-pdf-dialog-end", children: [
548
+ /* @__PURE__ */ jsx2("button", { type: "button", className: "w100n-pdf-link", onClick: () => setEditing(null), children: t.cancel }),
549
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-docs-action", disabled: !editing.body.trim() || editing.body.trim() === c.body || posting === "busy", onClick: () => void change(c.id, editing.body.trim()), children: [
550
+ /* @__PURE__ */ jsx2(Save, { "aria-hidden": true }),
551
+ t.save
552
+ ] })
553
+ ] })
554
+ ] }) : dropping === c.id ? /* @__PURE__ */ jsxs2("div", { className: "w100n-text-confirm", role: "group", "aria-label": t.delete, onClick: (e) => e.stopPropagation(), children: [
555
+ /* @__PURE__ */ jsx2("p", { children: c.replyTo || !all.some((r) => r.replyTo === c.id) ? t.deleteAsk : t.deleteThread }),
556
+ /* @__PURE__ */ jsxs2("span", { className: "w100n-pdf-dialog-end", children: [
557
+ /* @__PURE__ */ jsx2("button", { type: "button", className: "w100n-pdf-link", autoFocus: true, onClick: () => setDropping(null), children: t.cancel }),
558
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-docs-action", "data-tone": "red", disabled: posting === "busy", onClick: () => void change(c.id, null), children: [
559
+ /* @__PURE__ */ jsx2(Trash2, { "aria-hidden": true }),
560
+ t.delete
561
+ ] })
562
+ ] })
563
+ ] }) : /* @__PURE__ */ jsx2("p", { children: c.body });
564
+ const pick = (next) => setPop(pop === next ? null : next);
565
+ const swatches = (list, current, set, none) => /* @__PURE__ */ jsxs2("span", { className: "w100n-text-pop w100n-text-swatches", role: "group", children: [
566
+ /* @__PURE__ */ jsx2("button", { type: "button", className: "w100n-text-none", onClick: () => {
567
+ set(null);
568
+ setPop(null);
569
+ }, children: none }),
570
+ list.map((c, i) => /* @__PURE__ */ jsx2("button", { type: "button", "aria-label": c, "aria-pressed": current === c, onClick: () => {
571
+ set(c);
572
+ setPop(null);
573
+ }, children: /* @__PURE__ */ jsx2("i", { style: { background: c } }) }, i))
574
+ ] });
575
+ let tools = null;
576
+ if (shown === "home") tools = /* @__PURE__ */ jsxs2(Fragment3, { children: [
577
+ /* @__PURE__ */ jsxs2("span", { className: "w100n-text-anchor", children: [
578
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-text-picker", "data-pop": true, "aria-haspopup": "listbox", "aria-expanded": pop === "style", "aria-label": t.paragraphStyle, title: t.paragraphStyle, onClick: () => pick("style"), children: [
579
+ styles.find(([key]) => key === state.style)?.[1] ?? t.normal,
580
+ /* @__PURE__ */ jsx2(ChevronDown, { "aria-hidden": true })
581
+ ] }),
582
+ pop === "style" && /* @__PURE__ */ jsx2("span", { className: "w100n-text-pop w100n-text-styles", role: "listbox", "aria-label": t.paragraphStyle, children: styles.map(([key, label, run]) => /* @__PURE__ */ jsx2(
583
+ "button",
584
+ {
585
+ type: "button",
586
+ role: "option",
587
+ "data-style": key,
588
+ "aria-selected": state.style === key,
589
+ onClick: () => {
590
+ run();
591
+ setPop(null);
592
+ },
593
+ children: label
594
+ },
595
+ key
596
+ )) })
597
+ ] }),
598
+ gap,
599
+ tool(Bold, t.bold, () => editor.chain().focus().toggleBold().run(), state.bold),
600
+ tool(Italic, t.italic, () => editor.chain().focus().toggleItalic().run(), state.italic),
601
+ docx && tool(Underline, t.underline, () => editor.chain().focus().toggleUnderline().run(), state.underline),
602
+ tool(Strikethrough, t.strike, () => editor.chain().focus().toggleStrike().run(), state.strike),
603
+ !docx && tool(Code, t.code, () => editor.chain().focus().toggleCode().run(), state.code),
604
+ docx && /* @__PURE__ */ jsxs2("span", { className: "w100n-text-anchor", children: [
605
+ /* @__PURE__ */ jsxs2("button", { type: "button", "data-pop": true, "aria-expanded": pop === "color", "aria-label": t.textColour, title: t.textColour, onClick: () => pick("color"), children: [
606
+ /* @__PURE__ */ jsx2(Baseline, { "aria-hidden": true }),
607
+ /* @__PURE__ */ jsx2("i", { className: "w100n-text-bar", style: { background: state.color ?? "currentColor" } })
608
+ ] }),
609
+ pop === "color" && swatches(inks, state.color, (c) => c ? editor.chain().focus().setColor(c).run() : editor.chain().focus().unsetColor().run(), t.automatic)
610
+ ] }),
611
+ docx && /* @__PURE__ */ jsxs2("span", { className: "w100n-text-anchor", children: [
612
+ /* @__PURE__ */ jsx2("button", { type: "button", "data-pop": true, "aria-expanded": pop === "mark", "aria-pressed": state.glow, "aria-label": t.highlight, title: t.highlight, onClick: () => pick("mark"), children: /* @__PURE__ */ jsx2(Highlighter, { "aria-hidden": true }) }),
613
+ pop === "mark" && swatches(glows, null, (c) => c ? editor.chain().focus().setHighlight({ color: c }).run() : editor.chain().focus().unsetHighlight().run(), t.noColour)
614
+ ] }),
615
+ gap,
616
+ tool(List, t.bullets, () => editor.chain().focus().toggleBulletList().run(), state.bullets),
617
+ tool(ListOrdered, t.numbers, () => editor.chain().focus().toggleOrderedList().run(), state.numbers),
618
+ tool(ListChecks, t.tasks, () => editor.chain().focus().toggleTaskList().run(), state.tasks),
619
+ docx && /* @__PURE__ */ jsxs2(Fragment3, { children: [
620
+ gap,
621
+ [[AlignLeft, t.alignLeft, "left"], [AlignCenter, t.alignCenter, "center"], [AlignRight, t.alignRight, "right"], [AlignJustify, t.justify, "justify"]].map(([icon, label, value], i) => /* @__PURE__ */ jsx2(Fragment2, { children: tool(icon, label, () => align(editor, value), state.align === value) }, i))
622
+ ] }),
623
+ gap,
624
+ tool(ListIndentDecrease, t.outdent, () => indent(editor, -1)),
625
+ tool(ListIndentIncrease, t.indent, () => indent(editor, 1)),
626
+ tool(RemoveFormatting, t.clearFormat, () => clear(editor))
627
+ ] });
628
+ else if (shown === "insert") tools = /* @__PURE__ */ jsxs2(Fragment3, { children: [
629
+ /* @__PURE__ */ jsxs2("span", { className: "w100n-text-anchor", children: [
630
+ /* @__PURE__ */ jsx2("button", { type: "button", "data-pop": true, "aria-expanded": pop === "table", "aria-label": t.table, title: t.table, onClick: () => pick("table"), children: /* @__PURE__ */ jsx2(Table, { "aria-hidden": true }) }),
631
+ pop === "table" && /* @__PURE__ */ jsxs2("span", { className: "w100n-text-pop w100n-text-grid", role: "group", "aria-label": t.table, onPointerLeave: () => setGrid({ rows: 0, cols: 0 }), children: [
632
+ /* @__PURE__ */ jsx2("span", { children: Array.from({ length: 48 }, (_, i) => {
633
+ const rows = Math.floor(i / 8) + 1;
634
+ const cols = i % 8 + 1;
635
+ return /* @__PURE__ */ jsx2(
636
+ "button",
637
+ {
638
+ type: "button",
639
+ "aria-label": `${rows} \xD7 ${cols}`,
640
+ "data-on": rows <= grid.rows && cols <= grid.cols,
641
+ onPointerEnter: () => setGrid({ rows, cols }),
642
+ onClick: () => {
643
+ editor.chain().focus().insertTable({ rows, cols, withHeaderRow: true }).run();
644
+ setPop(null);
645
+ }
646
+ },
647
+ i
648
+ );
649
+ }) }),
650
+ /* @__PURE__ */ jsx2("small", { children: grid.rows ? `${grid.rows} \xD7 ${grid.cols}` : t.table })
651
+ ] })
652
+ ] }),
653
+ state.table && /* @__PURE__ */ jsxs2(Fragment3, { children: [
654
+ tool(BetweenHorizontalStart, t.rowAbove, () => editor.chain().focus().addRowBefore().run()),
655
+ tool(BetweenHorizontalEnd, t.rowBelow, () => editor.chain().focus().addRowAfter().run()),
656
+ tool(BetweenVerticalStart, t.columnLeft, () => editor.chain().focus().addColumnBefore().run()),
657
+ tool(BetweenVerticalEnd, t.columnRight, () => editor.chain().focus().addColumnAfter().run()),
658
+ tool(PanelTop, t.headerRow, () => editor.chain().focus().toggleHeaderRow().run()),
659
+ docx && tool(TableCellsMerge, t.mergeCells, () => editor.chain().focus().mergeOrSplit().run()),
660
+ tool(TableRowsSplit, t.deleteRow, () => editor.chain().focus().deleteRow().run()),
661
+ tool(TableColumnsSplit, t.deleteColumn, () => editor.chain().focus().deleteColumn().run()),
662
+ tool(Trash2, t.deleteTable, () => editor.chain().focus().deleteTable().run())
663
+ ] }),
664
+ gap,
665
+ /* @__PURE__ */ jsxs2("span", { className: "w100n-text-anchor", children: [
666
+ /* @__PURE__ */ jsx2("button", { type: "button", "data-pop": true, "aria-expanded": pop === "image", "aria-label": t.image, title: t.image, onClick: () => {
667
+ setHref("");
668
+ pick("image");
669
+ }, children: /* @__PURE__ */ jsx2(ImagePlus, { "aria-hidden": true }) }),
670
+ pop === "image" && /* @__PURE__ */ jsxs2("span", { className: "w100n-text-pop w100n-text-form", children: [
671
+ /* @__PURE__ */ jsx2(
672
+ "input",
673
+ {
674
+ autoFocus: true,
675
+ value: href,
676
+ placeholder: t.imageLink,
677
+ "aria-label": t.imageLink,
678
+ onChange: (e) => setHref(e.target.value),
679
+ onKeyDown: (e) => {
680
+ if (e.key === "Enter" && href.trim()) {
681
+ editor.chain().focus().setImage({ src: href.trim() }).run();
682
+ setPop(null);
683
+ }
684
+ }
685
+ }
686
+ ),
687
+ /* @__PURE__ */ jsxs2("span", { className: "w100n-docs-row", children: [
688
+ docx && /* @__PURE__ */ jsxs2("label", { className: "w100n-pdf-link", children: [
689
+ t.chooseFile,
690
+ /* @__PURE__ */ jsx2("input", { type: "file", accept: "image/png,image/jpeg,image/gif", hidden: true, onChange: (e) => {
691
+ const file = e.target.files?.[0];
692
+ if (!file) return;
693
+ const reader = new FileReader();
694
+ reader.onload = () => {
695
+ editor.chain().focus().setImage({ src: String(reader.result), alt: file.name }).run();
696
+ setPop(null);
697
+ };
698
+ reader.readAsDataURL(file);
699
+ } })
700
+ ] }),
701
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-docs-action", disabled: !href.trim(), onClick: () => {
702
+ editor.chain().focus().setImage({ src: href.trim() }).run();
703
+ setPop(null);
704
+ }, children: [
705
+ /* @__PURE__ */ jsx2(ImagePlus, { "aria-hidden": true }),
706
+ t.put
707
+ ] })
708
+ ] })
709
+ ] })
710
+ ] }),
711
+ /* @__PURE__ */ jsxs2("span", { className: "w100n-text-anchor", children: [
712
+ /* @__PURE__ */ jsx2("button", { type: "button", "data-pop": true, "aria-expanded": pop === "link", "aria-pressed": state.link, "aria-label": t.link, title: t.link, onClick: () => pop === "link" ? setPop(null) : linkUp(), children: /* @__PURE__ */ jsx2(Link, { "aria-hidden": true }) }),
713
+ pop === "link" && /* @__PURE__ */ jsxs2("span", { className: "w100n-text-pop w100n-text-form", children: [
714
+ /* @__PURE__ */ jsx2("input", { autoFocus: true, value: href, placeholder: t.linkAddress, "aria-label": t.linkAddress, onChange: (e) => setHref(e.target.value), onKeyDown: (e) => {
715
+ if (e.key === "Enter") putLink();
716
+ } }),
717
+ /* @__PURE__ */ jsxs2("span", { className: "w100n-docs-row", children: [
718
+ state.link && /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-pdf-link", onClick: () => {
719
+ editor.chain().focus().extendMarkRange("link").unsetLink().run();
720
+ setPop(null);
721
+ }, children: [
722
+ /* @__PURE__ */ jsx2(Link2Off, { "aria-hidden": true }),
723
+ t.unlink
724
+ ] }),
725
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-docs-action", disabled: !href.trim(), onClick: putLink, children: [
726
+ /* @__PURE__ */ jsx2(Link, { "aria-hidden": true }),
727
+ t.apply
728
+ ] })
729
+ ] })
730
+ ] })
731
+ ] }),
732
+ docx && tool(SeparatorHorizontal, t.pageBreak, () => editor.chain().focus().insertContent({ type: "pageBreak" }).run()),
733
+ tool(Minus, t.rule, () => editor.chain().focus().setHorizontalRule().run())
734
+ ] });
735
+ else if (shown === "layout") tools = /* @__PURE__ */ jsxs2(Fragment3, { children: [
736
+ /* @__PURE__ */ jsx2("span", { className: "w100n-text-label", children: t.pageSize }),
737
+ [["A4", a4], ["Letter", letter]].map(([label, size], i) => /* @__PURE__ */ jsx2(
738
+ "button",
739
+ {
740
+ type: "button",
741
+ className: "w100n-text-word",
742
+ "aria-pressed": Math.abs(page.width - size.width) < 2 && Math.abs(page.height - size.height) < 2,
743
+ onClick: () => setLayout({ ...page, width: size.width, height: size.height }),
744
+ children: label
745
+ },
746
+ i
747
+ )),
748
+ gap,
749
+ /* @__PURE__ */ jsx2("span", { className: "w100n-text-label", children: t.margins }),
750
+ margins.map(([label, values], i) => /* @__PURE__ */ jsx2(
751
+ "button",
752
+ {
753
+ type: "button",
754
+ className: "w100n-text-word",
755
+ "aria-pressed": values.every((v, n) => Math.abs(page.margins[n] - v) < 2),
756
+ onClick: () => setLayout({ ...page, margins: values }),
757
+ children: label
758
+ },
759
+ i
760
+ ))
761
+ ] });
762
+ else if (shown === "refs") tools = /* @__PURE__ */ jsxs2(Fragment3, { children: [
763
+ state.toc < 0 ? /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-docs-action", onClick: () => {
764
+ const at = editor.state.selection.$from.before(1);
765
+ editor.chain().focus().insertContentAt(at, { type: "toc", attrs: { entries: JSON.stringify(entries(editor.state.doc, !docx, at - 1)) } }).run();
766
+ }, children: [
767
+ /* @__PURE__ */ jsx2(TableOfContents, { "aria-hidden": true }),
768
+ t.tocInsert
769
+ ] }) : /* @__PURE__ */ jsxs2(Fragment3, { children: [
770
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-text-word", onClick: () => editor.view.nodeDOM(state.toc)?.scrollIntoView({ behavior: "smooth", block: "start" }), children: [
771
+ /* @__PURE__ */ jsx2(TableOfContents, { "aria-hidden": true }),
772
+ t.tocGo
773
+ ] }),
774
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-docs-action", "data-tone": "red", onClick: () => editor.chain().focus().deleteRange({ from: state.toc, to: state.toc + editor.state.doc.nodeAt(state.toc).nodeSize }).run(), children: [
775
+ /* @__PURE__ */ jsx2(Trash2, { "aria-hidden": true }),
776
+ t.tocRemove
777
+ ] })
778
+ ] }),
779
+ /* @__PURE__ */ jsx2("span", { className: "w100n-text-label", children: t.tocHint })
780
+ ] });
781
+ else if (shown === "review") tools = /* @__PURE__ */ jsxs2(Fragment3, { children: [
782
+ can.comment && /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-docs-action", disabled: state.empty, onClick: begin, children: [
783
+ /* @__PURE__ */ jsx2(MessageSquarePlus, { "aria-hidden": true }),
784
+ t.addComment
785
+ ] }),
786
+ docx && can.edit && /* @__PURE__ */ jsxs2(Fragment3, { children: [
787
+ /* @__PURE__ */ jsxs2(
788
+ "button",
789
+ {
790
+ type: "button",
791
+ className: "w100n-text-word",
792
+ "aria-pressed": state.suggest,
793
+ onClick: () => editor.view.dispatch(editor.state.tr.setMeta(suggesting, { on: !state.suggest, me: host?.me.name ?? "" })),
794
+ children: [
795
+ /* @__PURE__ */ jsx2(FilePenLine, { "aria-hidden": true }),
796
+ t.suggest
797
+ ]
798
+ }
799
+ ),
800
+ state.revs.length > 0 && /* @__PURE__ */ jsxs2(Fragment3, { children: [
801
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-docs-action", "data-tone": "green", onClick: () => decide(editor, true), children: [
802
+ /* @__PURE__ */ jsx2(CheckCheck, { "aria-hidden": true }),
803
+ t.acceptAll
804
+ ] }),
805
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-docs-action", "data-tone": "red", onClick: () => decide(editor, false), children: [
806
+ /* @__PURE__ */ jsx2(X, { "aria-hidden": true }),
807
+ t.rejectAll
808
+ ] })
809
+ ] })
810
+ ] }),
811
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-text-word", onClick: () => onPanel?.("comments"), children: [
812
+ /* @__PURE__ */ jsx2(MessagesSquare, { "aria-hidden": true }),
813
+ t.comments,
814
+ /* @__PURE__ */ jsx2("b", { children: open.length })
815
+ ] })
816
+ ] });
817
+ else tools = /* @__PURE__ */ jsxs2(Fragment3, { children: [
818
+ tool(Minus, t.zoomOut, () => zoomBy(-10)),
819
+ /* @__PURE__ */ jsxs2("span", { className: "w100n-pdf-zoom", children: [
820
+ percent,
821
+ "%"
822
+ ] }),
823
+ tool(Plus, t.zoomIn, () => zoomBy(10)),
824
+ docx && tool(MoveHorizontal, t.fitWidth, () => setZoom(null), zoom === null),
825
+ tool(Maximize2, t.fit, () => setZoom(100)),
826
+ gap,
827
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-text-word", onClick: () => onPanel?.("outline"), children: [
828
+ /* @__PURE__ */ jsx2(ListTree, { "aria-hidden": true }),
829
+ t.outline
830
+ ] })
831
+ ] });
832
+ const sheet = docx ? { "--page-w": `${page.width}px`, "--page-h": `${page.height}px`, "--mt": `${page.margins[0]}px`, "--mr": `${page.margins[1]}px`, "--mb": `${page.margins[2]}px`, "--ml": `${page.margins[3]}px` } : void 0;
833
+ return /* @__PURE__ */ jsxs2("div", { className: "w100n-text", "data-kind": kind, onKeyDown: keys, children: [
834
+ /* @__PURE__ */ jsxs2("div", { className: "w100n-text-ribbon", role: "toolbar", "aria-label": t.textTools, onKeyDown: rove, onPointerDown: (e) => {
835
+ if (e.target.closest("button")) e.preventDefault();
836
+ }, children: [
837
+ (tabs.length > 1 || live) && /* @__PURE__ */ jsxs2("div", { className: "w100n-text-tabs", children: [
838
+ /* @__PURE__ */ jsx2("span", { role: "tablist", "aria-label": t.textTools, children: tabs.length > 1 && tabs.map(([key, label]) => /* @__PURE__ */ jsx2("button", { type: "button", role: "tab", "aria-selected": shown === key, onClick: () => {
839
+ setTab(key);
840
+ setPop(null);
841
+ }, children: label }, key)) }),
842
+ (can.edit || live) && /* @__PURE__ */ jsxs2("span", { className: "w100n-text-end", children: [
843
+ live && (live.ready ? !live.online : live.late || live.denied) && /* @__PURE__ */ jsxs2("span", { className: "w100n-text-live", title: live.denied ? void 0 : t.offlineHint, children: [
844
+ /* @__PURE__ */ jsx2(WifiOff, { "aria-hidden": true }),
845
+ live.denied ? t.liveOff : t.offline
846
+ ] }),
847
+ live && live.people.length > 0 && /* @__PURE__ */ jsx2("span", { className: "w100n-text-people", role: "group", "aria-label": t.people, children: live.people.map((p) => /* @__PURE__ */ jsx2("span", { className: "w100n-text-face", title: p.me ? `${p.name} (${t.you})` : p.name, style: { "--face": p.color }, children: p.avatarUrl ? /* @__PURE__ */ jsx2("img", { src: p.avatarUrl, alt: "" }) : p.name.slice(0, 1).toUpperCase() }, p.id)) }),
848
+ state.suggest && /* @__PURE__ */ jsx2("span", { className: "w100n-docs-badge", children: t.suggesting }),
849
+ can.edit && /* @__PURE__ */ jsxs2(Fragment3, { children: [
850
+ tool(Undo2, t.undo, () => editor.chain().focus().undo().run(), void 0, !state.undo),
851
+ tool(Redo2, t.redo, () => editor.chain().focus().redo().run(), void 0, !state.redo),
852
+ saving === "failed" && /* @__PURE__ */ jsx2("span", { className: "w100n-docs-muted", children: t.saveFailed }),
853
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-docs-action", disabled: !dirty || saving === "busy" || !!live && !live.online, onClick: () => void save(), children: [
854
+ /* @__PURE__ */ jsx2(Save, { "aria-hidden": true }),
855
+ saving === "busy" ? t.saving : !dirty && saving === "done" ? t.saved : t.save
856
+ ] })
857
+ ] })
858
+ ] })
859
+ ] }),
860
+ /* @__PURE__ */ jsx2("div", { className: "w100n-text-tools", role: "group", "aria-label": tabs.find(([key]) => key === shown)?.[1], children: tools })
861
+ ] }),
862
+ /* @__PURE__ */ jsx2("div", { ref: scroller, className: "w100n-text-scroll", onClick: (e) => {
863
+ const id = e.target.closest("[data-note]")?.getAttribute("data-note");
864
+ if (!id) return;
865
+ setActive(id);
866
+ onPanel?.("comments");
867
+ }, children: /* @__PURE__ */ jsx2("div", { className: "w100n-text-zoomed", style: { zoom: percent / 100 }, children: /* @__PURE__ */ jsx2("div", { className: docx ? "w100n-text-sheet" : "w100n-docs-page w100n-text-column", style: sheet, children: /* @__PURE__ */ jsx2(EditorContent, { editor }) }) }) }),
868
+ slot && createPortal(/* @__PURE__ */ jsxs2("div", { className: "w100n-text-notes", children: [
869
+ draft && /* @__PURE__ */ jsxs2("div", { className: "w100n-text-card w100n-text-draft", children: [
870
+ /* @__PURE__ */ jsx2("blockquote", { children: draft.quote }),
871
+ /* @__PURE__ */ jsx2(
872
+ "textarea",
873
+ {
874
+ autoFocus: true,
875
+ value: draft.body,
876
+ placeholder: t.writeComment,
877
+ "aria-label": t.writeComment,
878
+ onChange: (e) => setDraft({ ...draft, body: e.target.value }),
879
+ onKeyDown: (e) => {
880
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) void post();
881
+ }
882
+ }
883
+ ),
884
+ /* @__PURE__ */ jsxs2("span", { className: "w100n-pdf-dialog-end", children: [
885
+ /* @__PURE__ */ jsx2("button", { type: "button", className: "w100n-pdf-link", onClick: drop, children: t.cancel }),
886
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-docs-action", disabled: !draft.body.trim() || posting === "busy", onClick: () => void post(), children: [
887
+ /* @__PURE__ */ jsx2(MessageSquarePlus, { "aria-hidden": true }),
888
+ t.post
889
+ ] })
890
+ ] })
891
+ ] }),
892
+ suggested.length > 0 && /* @__PURE__ */ jsxs2(Fragment3, { children: [
893
+ /* @__PURE__ */ jsxs2("p", { className: "w100n-docs-caption", children: [
894
+ /* @__PURE__ */ jsx2(FilePenLine, { "aria-hidden": true }),
895
+ t.suggestions,
896
+ /* @__PURE__ */ jsx2("span", { children: suggested.length })
897
+ ] }),
898
+ suggested.map((c, i) => /* @__PURE__ */ jsxs2("article", { className: "w100n-text-card w100n-text-change", "data-kind": c.kind, onClick: () => jump(c.from), children: [
899
+ /* @__PURE__ */ jsxs2("header", { children: [
900
+ /* @__PURE__ */ jsx2("span", { className: "w100n-text-face", "aria-hidden": true, children: (c.author || "?").slice(0, 1).toUpperCase() }),
901
+ /* @__PURE__ */ jsxs2("span", { className: "w100n-text-who", children: [
902
+ /* @__PURE__ */ jsx2("b", { children: c.author || t.someone }),
903
+ c.date && /* @__PURE__ */ jsx2("small", { children: when(c.date) })
904
+ ] })
905
+ ] }),
906
+ /* @__PURE__ */ jsxs2("p", { children: [
907
+ c.kind === "insertion" ? t.added : t.removed,
908
+ " ",
909
+ /* @__PURE__ */ jsx2("q", { children: c.text })
910
+ ] }),
911
+ can.edit && /* @__PURE__ */ jsxs2("span", { className: "w100n-text-actions", onClick: (e) => e.stopPropagation(), children: [
912
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-docs-action", "data-tone": "green", onClick: () => decide(editor, true, c.id), children: [
913
+ /* @__PURE__ */ jsx2(Check, { "aria-hidden": true }),
914
+ t.accept
915
+ ] }),
916
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-docs-action", "data-tone": "red", onClick: () => decide(editor, false, c.id), children: [
917
+ /* @__PURE__ */ jsx2(X, { "aria-hidden": true }),
918
+ t.reject
919
+ ] })
920
+ ] })
921
+ ] }, i))
922
+ ] }),
923
+ posting === "failed" && /* @__PURE__ */ jsx2("p", { className: "w100n-docs-muted", role: "alert", children: t.commentFailed }),
924
+ failed && /* @__PURE__ */ jsx2("p", { className: "w100n-docs-muted", role: "alert", children: t.changeFailed }),
925
+ /* @__PURE__ */ jsxs2("div", { className: "w100n-docs-row", children: [
926
+ /* @__PURE__ */ jsxs2("p", { className: "w100n-docs-caption", children: [
927
+ /* @__PURE__ */ jsx2(MessagesSquare, { "aria-hidden": true }),
928
+ t.comments,
929
+ /* @__PURE__ */ jsx2("span", { children: open.length })
930
+ ] }),
931
+ /* @__PURE__ */ jsx2("span", { className: "w100n-docs-chips", children: /* @__PURE__ */ jsx2("button", { type: "button", "aria-pressed": resolved, onClick: () => setResolved(!resolved), children: t.showResolved }) })
932
+ ] }),
933
+ !threads.length && !draft && /* @__PURE__ */ jsx2("p", { className: "w100n-docs-muted", children: can.comment ? t.commentHint : t.noComments }),
934
+ threads.map((c) => /* @__PURE__ */ jsxs2("article", { className: "w100n-text-card", "aria-current": active === c.id, "data-resolved": c.resolved, onClick: () => show(c), children: [
935
+ person(c),
936
+ c.quote && /* @__PURE__ */ jsx2("blockquote", { children: c.quote }),
937
+ words(c),
938
+ all.filter((r) => r.replyTo === c.id).map((r) => /* @__PURE__ */ jsxs2("div", { className: "w100n-text-reply", children: [
939
+ person(r),
940
+ words(r)
941
+ ] }, r.id)),
942
+ reply?.id === c.id && /* @__PURE__ */ jsxs2("div", { className: "w100n-text-reply", onClick: (e) => e.stopPropagation(), children: [
943
+ /* @__PURE__ */ jsx2(
944
+ "textarea",
945
+ {
946
+ autoFocus: true,
947
+ value: reply.body,
948
+ placeholder: t.writeComment,
949
+ "aria-label": t.reply,
950
+ onChange: (e) => setReply({ ...reply, body: e.target.value }),
951
+ onKeyDown: (e) => {
952
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) void answer(c);
953
+ }
954
+ }
955
+ ),
956
+ /* @__PURE__ */ jsxs2("span", { className: "w100n-pdf-dialog-end", children: [
957
+ /* @__PURE__ */ jsx2("button", { type: "button", className: "w100n-pdf-link", onClick: () => setReply(null), children: t.cancel }),
958
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-docs-action", disabled: !reply.body.trim() || posting === "busy", onClick: () => void answer(c), children: [
959
+ /* @__PURE__ */ jsx2(Reply, { "aria-hidden": true }),
960
+ t.reply
961
+ ] })
962
+ ] })
963
+ ] }),
964
+ can.comment && reply?.id !== c.id && /* @__PURE__ */ jsxs2("span", { className: "w100n-text-actions", onClick: (e) => e.stopPropagation(), children: [
965
+ c.resolved && /* @__PURE__ */ jsx2("span", { className: "w100n-docs-badge", children: t.resolved }),
966
+ !c.resolved && /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-docs-action", onClick: () => setReply({ id: c.id, body: "" }), children: [
967
+ /* @__PURE__ */ jsx2(Reply, { "aria-hidden": true }),
968
+ t.reply
969
+ ] }),
970
+ host?.resolveComment && /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-docs-action", "data-tone": "green", onClick: () => void settle(c), children: [
971
+ c.resolved ? /* @__PURE__ */ jsx2(RotateCcw, { "aria-hidden": true }) : /* @__PURE__ */ jsx2(Check, { "aria-hidden": true }),
972
+ c.resolved ? t.reopen : t.resolve
973
+ ] })
974
+ ] })
975
+ ] }, c.id))
976
+ ] }), slot)
977
+ ] });
978
+ }
979
+ export {
980
+ TextEditor as default
981
+ };
982
+ //# sourceMappingURL=TextEditor-X3X62R46.js.map