@w100n/documents 0.4.0 → 0.11.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 (61) hide show
  1. package/dist/SheetView-3XTEV2VT.css +319 -0
  2. package/dist/SheetView-3XTEV2VT.css.map +1 -0
  3. package/dist/SheetView-TXHS5WJC.js +928 -0
  4. package/dist/SheetView-TXHS5WJC.js.map +1 -0
  5. package/dist/TextEditor-4YETNX4T.js +983 -0
  6. package/dist/TextEditor-4YETNX4T.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-EM4U6OC5.js +3447 -0
  11. package/dist/chunk-EM4U6OC5.js.map +1 -0
  12. package/dist/chunk-FPGPEIW5.js +196 -0
  13. package/dist/chunk-FPGPEIW5.js.map +1 -0
  14. package/dist/chunk-ISOOUYLI.js +150 -0
  15. package/dist/chunk-ISOOUYLI.js.map +1 -0
  16. package/dist/{chunk-KEV4R7W5.js → chunk-L765DZPO.js} +9 -1
  17. package/dist/chunk-L765DZPO.js.map +1 -0
  18. package/dist/chunk-QDH7EZAF.js +27 -0
  19. package/dist/chunk-QDH7EZAF.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 +12191 -486
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.css +3632 -728
  35. package/dist/index.css.map +1 -1
  36. package/dist/index.d.cts +144 -11
  37. package/dist/index.d.ts +144 -11
  38. package/dist/index.js +3389 -439
  39. package/dist/index.js.map +1 -1
  40. package/dist/kinds.cjs +71 -0
  41. package/dist/kinds.cjs.map +1 -0
  42. package/dist/kinds.d.cts +11 -0
  43. package/dist/kinds.d.ts +11 -0
  44. package/dist/kinds.js +14 -0
  45. package/dist/kinds.js.map +1 -0
  46. package/dist/memory.cjs +59 -7
  47. package/dist/memory.cjs.map +1 -1
  48. package/dist/memory.d.cts +7 -2
  49. package/dist/memory.d.ts +7 -2
  50. package/dist/memory.js +57 -8
  51. package/dist/memory.js.map +1 -1
  52. package/dist/pdfWrite-EM6GJDBF.js +76 -0
  53. package/dist/pdfWrite-EM6GJDBF.js.map +1 -0
  54. package/dist/types-B5oF25dn.d.cts +165 -0
  55. package/dist/types-B5oF25dn.d.ts +165 -0
  56. package/dist/write-DaVtTJCS.d.cts +6 -0
  57. package/dist/write-DaVtTJCS.d.ts +6 -0
  58. package/package.json +50 -3
  59. package/dist/chunk-KEV4R7W5.js.map +0 -1
  60. package/dist/types-DshXPnWA.d.cts +0 -62
  61. package/dist/types-DshXPnWA.d.ts +0 -62
@@ -0,0 +1,983 @@
1
+ 'use client';
2
+ import {
3
+ useLoad
4
+ } from "./chunk-QDH7EZAF.js";
5
+ import {
6
+ useLive
7
+ } from "./chunk-FPGPEIW5.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-EM4U6OC5.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 drops = (c) => mine(c) || c.author.id.startsWith("guest:") && !!rights?.edit;
439
+ const show = (c) => {
440
+ setActive(c.id);
441
+ const spot = notes.getState(editor.state).spots.find((s) => s.id === c.id);
442
+ if (!spot) return;
443
+ const at = editor.view.domAtPos(spot.from).node;
444
+ (at instanceof HTMLElement ? at : at.parentElement)?.scrollIntoView({ behavior: "smooth", block: "center" });
445
+ };
446
+ const suggested = [...state.revs.reduce((all2, c) => {
447
+ const key = `${c.kind}:${c.id}`;
448
+ const was = all2.get(key);
449
+ all2.set(key, was ? { ...was, text: `${was.text} ${c.text}` } : c);
450
+ return all2;
451
+ }, /* @__PURE__ */ new Map()).values()];
452
+ const jump = (at) => {
453
+ const dom = editor.view.domAtPos(at).node;
454
+ (dom instanceof HTMLElement ? dom : dom.parentElement)?.scrollIntoView({ behavior: "smooth", block: "center" });
455
+ };
456
+ const percent = zoom ?? (docx && room ? Math.max(10, Math.min(100, Math.floor((room - 24) / page.width * 100))) : 100);
457
+ const zoomBy = (d) => setZoom(Math.min(200, Math.max(30, percent + d)));
458
+ const keys = (e) => {
459
+ if (e.defaultPrevented) return;
460
+ const key = e.key.toLowerCase();
461
+ const mod = e.metaKey || e.ctrlKey;
462
+ if (key === "escape" && (pop || draft)) {
463
+ e.stopPropagation();
464
+ if (pop) setPop(null);
465
+ else drop();
466
+ return;
467
+ }
468
+ if (!mod) return;
469
+ const act = (run) => {
470
+ e.preventDefault();
471
+ run();
472
+ };
473
+ if (key === "s" && can.edit) act(() => void save());
474
+ else if (key === "k" && can.edit) act(linkUp);
475
+ else if (key === "m" && e.altKey && can.comment) act(begin);
476
+ else if (!can.edit || e.altKey) return;
477
+ else if (key === "]" || key === "[") act(() => indent(editor, key === "]" ? 1 : -1));
478
+ else if (key === "\\") act(() => clear(editor));
479
+ else if (key === "enter" && docx) act(() => editor.chain().focus().insertContent({ type: "pageBreak" }).run());
480
+ else if (e.shiftKey && docx && ["l", "e", "r", "j"].includes(key)) act(() => align(editor, { l: "left", e: "center", r: "right", j: "justify" }[key]));
481
+ };
482
+ const tool = (icon, label, run, pressed, disabled) => {
483
+ const Icon = icon;
484
+ return /* @__PURE__ */ jsx2("button", { type: "button", "aria-label": label, title: label, "aria-pressed": pressed, disabled, onClick: () => {
485
+ run();
486
+ }, children: /* @__PURE__ */ jsx2(Icon, { "aria-hidden": true }) });
487
+ };
488
+ const gap = /* @__PURE__ */ jsx2("span", { className: "w100n-pdf-gap" });
489
+ const known = source.data.styles;
490
+ const named = (test) => {
491
+ const name = known.find(test)?.name;
492
+ return name && `${name[0].toUpperCase()}${name.slice(1)}`;
493
+ };
494
+ const levels = docx ? [1, 2, 3, 4, 5, 6].filter((level) => level <= 4 || named((s) => s.heading === level)) : [1, 2, 3, 4];
495
+ const styles = [
496
+ ["normal", t.normal, () => editor.chain().focus().clearNodes().setParagraph().run()],
497
+ ...levels.map((level) => [
498
+ `h${level}`,
499
+ named((s) => s.heading === level && !/^title$/i.test(s.name)) || `${t.heading} ${level}`,
500
+ () => editor.chain().focus().setHeading({ level }).run()
501
+ ]),
502
+ ["quote", named((s) => /quote/i.test(s.name)) || t.quote, () => editor.chain().focus().clearNodes().setParagraph().setBlockquote().run()],
503
+ ["code", t.code, () => editor.chain().focus().setCodeBlock().run()]
504
+ ];
505
+ const tabs = [
506
+ ...can.edit ? [["home", t.home], ["insert", t.insert]] : [],
507
+ ...can.edit && docx ? [["layout", t.layout]] : [],
508
+ ...can.edit ? [["refs", t.references]] : [],
509
+ ...host?.comments && (can.comment || open.length > 0) ? [["review", t.review]] : [],
510
+ ["view", t.viewTab]
511
+ ];
512
+ const shown = tabs.some(([key]) => key === tab) ? tab : "view";
513
+ const margins = [[t.marginsNormal, [96, 96, 96, 96]], [t.marginsNarrow, [48, 48, 48, 48]], [t.marginsWide, [96, 192, 96, 192]]];
514
+ const when = (iso) => new Intl.DateTimeFormat(language, { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" }).format(new Date(iso));
515
+ const person = (c) => /* @__PURE__ */ jsxs2("header", { children: [
516
+ 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() }),
517
+ /* @__PURE__ */ jsxs2("span", { className: "w100n-text-who", children: [
518
+ /* @__PURE__ */ jsxs2("b", { children: [
519
+ c.author.name,
520
+ c.author.id.startsWith("guest:") && /* @__PURE__ */ jsx2("span", { className: "w100n-docs-badge", children: t.guest })
521
+ ] }),
522
+ /* @__PURE__ */ jsx2("small", { children: when(c.createdAt) })
523
+ ] }),
524
+ drops(c) && /* @__PURE__ */ jsxs2("span", { className: "w100n-text-own", onClick: (e) => e.stopPropagation(), children: [
525
+ mine(c) && host.editComment && /* @__PURE__ */ jsx2("button", { type: "button", "aria-label": t.editComment, title: t.editComment, onClick: () => {
526
+ setDropping(null);
527
+ setEditing({ id: c.id, body: c.body });
528
+ }, children: /* @__PURE__ */ jsx2(Pencil, { "aria-hidden": true }) }),
529
+ host.deleteComment && /* @__PURE__ */ jsx2("button", { type: "button", "data-tone": "red", "aria-label": t.delete, title: t.delete, onClick: () => {
530
+ setEditing(null);
531
+ setDropping(c.id);
532
+ }, children: /* @__PURE__ */ jsx2(Trash2, { "aria-hidden": true }) })
533
+ ] })
534
+ ] });
535
+ const words = (c) => editing?.id === c.id ? /* @__PURE__ */ jsxs2("div", { className: "w100n-text-reply w100n-text-edit", onClick: (e) => e.stopPropagation(), children: [
536
+ /* @__PURE__ */ jsx2(
537
+ "textarea",
538
+ {
539
+ autoFocus: true,
540
+ value: editing.body,
541
+ "aria-label": t.editComment,
542
+ onChange: (e) => setEditing({ ...editing, body: e.target.value }),
543
+ onKeyDown: (e) => {
544
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey) && editing.body.trim()) void change(c.id, editing.body.trim());
545
+ }
546
+ }
547
+ ),
548
+ /* @__PURE__ */ jsxs2("span", { className: "w100n-pdf-dialog-end", children: [
549
+ /* @__PURE__ */ jsx2("button", { type: "button", className: "w100n-pdf-link", onClick: () => setEditing(null), children: t.cancel }),
550
+ /* @__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: [
551
+ /* @__PURE__ */ jsx2(Save, { "aria-hidden": true }),
552
+ t.save
553
+ ] })
554
+ ] })
555
+ ] }) : dropping === c.id ? /* @__PURE__ */ jsxs2("div", { className: "w100n-text-confirm", role: "group", "aria-label": t.delete, onClick: (e) => e.stopPropagation(), children: [
556
+ /* @__PURE__ */ jsx2("p", { children: c.replyTo || !all.some((r) => r.replyTo === c.id) ? t.deleteAsk : t.deleteThread }),
557
+ /* @__PURE__ */ jsxs2("span", { className: "w100n-pdf-dialog-end", children: [
558
+ /* @__PURE__ */ jsx2("button", { type: "button", className: "w100n-pdf-link", autoFocus: true, onClick: () => setDropping(null), children: t.cancel }),
559
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-docs-action", "data-tone": "red", disabled: posting === "busy", onClick: () => void change(c.id, null), children: [
560
+ /* @__PURE__ */ jsx2(Trash2, { "aria-hidden": true }),
561
+ t.delete
562
+ ] })
563
+ ] })
564
+ ] }) : /* @__PURE__ */ jsx2("p", { children: c.body });
565
+ const pick = (next) => setPop(pop === next ? null : next);
566
+ const swatches = (list, current, set, none) => /* @__PURE__ */ jsxs2("span", { className: "w100n-text-pop w100n-text-swatches", role: "group", children: [
567
+ /* @__PURE__ */ jsx2("button", { type: "button", className: "w100n-text-none", onClick: () => {
568
+ set(null);
569
+ setPop(null);
570
+ }, children: none }),
571
+ list.map((c, i) => /* @__PURE__ */ jsx2("button", { type: "button", "aria-label": c, "aria-pressed": current === c, onClick: () => {
572
+ set(c);
573
+ setPop(null);
574
+ }, children: /* @__PURE__ */ jsx2("i", { style: { background: c } }) }, i))
575
+ ] });
576
+ let tools = null;
577
+ if (shown === "home") tools = /* @__PURE__ */ jsxs2(Fragment3, { children: [
578
+ /* @__PURE__ */ jsxs2("span", { className: "w100n-text-anchor", children: [
579
+ /* @__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: [
580
+ styles.find(([key]) => key === state.style)?.[1] ?? t.normal,
581
+ /* @__PURE__ */ jsx2(ChevronDown, { "aria-hidden": true })
582
+ ] }),
583
+ 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(
584
+ "button",
585
+ {
586
+ type: "button",
587
+ role: "option",
588
+ "data-style": key,
589
+ "aria-selected": state.style === key,
590
+ onClick: () => {
591
+ run();
592
+ setPop(null);
593
+ },
594
+ children: label
595
+ },
596
+ key
597
+ )) })
598
+ ] }),
599
+ gap,
600
+ tool(Bold, t.bold, () => editor.chain().focus().toggleBold().run(), state.bold),
601
+ tool(Italic, t.italic, () => editor.chain().focus().toggleItalic().run(), state.italic),
602
+ docx && tool(Underline, t.underline, () => editor.chain().focus().toggleUnderline().run(), state.underline),
603
+ tool(Strikethrough, t.strike, () => editor.chain().focus().toggleStrike().run(), state.strike),
604
+ !docx && tool(Code, t.code, () => editor.chain().focus().toggleCode().run(), state.code),
605
+ docx && /* @__PURE__ */ jsxs2("span", { className: "w100n-text-anchor", children: [
606
+ /* @__PURE__ */ jsxs2("button", { type: "button", "data-pop": true, "aria-expanded": pop === "color", "aria-label": t.textColour, title: t.textColour, onClick: () => pick("color"), children: [
607
+ /* @__PURE__ */ jsx2(Baseline, { "aria-hidden": true }),
608
+ /* @__PURE__ */ jsx2("i", { className: "w100n-text-bar", style: { background: state.color ?? "currentColor" } })
609
+ ] }),
610
+ pop === "color" && swatches(inks, state.color, (c) => c ? editor.chain().focus().setColor(c).run() : editor.chain().focus().unsetColor().run(), t.automatic)
611
+ ] }),
612
+ docx && /* @__PURE__ */ jsxs2("span", { className: "w100n-text-anchor", children: [
613
+ /* @__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 }) }),
614
+ pop === "mark" && swatches(glows, null, (c) => c ? editor.chain().focus().setHighlight({ color: c }).run() : editor.chain().focus().unsetHighlight().run(), t.noColour)
615
+ ] }),
616
+ gap,
617
+ tool(List, t.bullets, () => editor.chain().focus().toggleBulletList().run(), state.bullets),
618
+ tool(ListOrdered, t.numbers, () => editor.chain().focus().toggleOrderedList().run(), state.numbers),
619
+ tool(ListChecks, t.tasks, () => editor.chain().focus().toggleTaskList().run(), state.tasks),
620
+ docx && /* @__PURE__ */ jsxs2(Fragment3, { children: [
621
+ gap,
622
+ [[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))
623
+ ] }),
624
+ gap,
625
+ tool(ListIndentDecrease, t.outdent, () => indent(editor, -1)),
626
+ tool(ListIndentIncrease, t.indent, () => indent(editor, 1)),
627
+ tool(RemoveFormatting, t.clearFormat, () => clear(editor))
628
+ ] });
629
+ else if (shown === "insert") tools = /* @__PURE__ */ jsxs2(Fragment3, { children: [
630
+ /* @__PURE__ */ jsxs2("span", { className: "w100n-text-anchor", children: [
631
+ /* @__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 }) }),
632
+ 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: [
633
+ /* @__PURE__ */ jsx2("span", { children: Array.from({ length: 48 }, (_, i) => {
634
+ const rows = Math.floor(i / 8) + 1;
635
+ const cols = i % 8 + 1;
636
+ return /* @__PURE__ */ jsx2(
637
+ "button",
638
+ {
639
+ type: "button",
640
+ "aria-label": `${rows} \xD7 ${cols}`,
641
+ "data-on": rows <= grid.rows && cols <= grid.cols,
642
+ onPointerEnter: () => setGrid({ rows, cols }),
643
+ onClick: () => {
644
+ editor.chain().focus().insertTable({ rows, cols, withHeaderRow: true }).run();
645
+ setPop(null);
646
+ }
647
+ },
648
+ i
649
+ );
650
+ }) }),
651
+ /* @__PURE__ */ jsx2("small", { children: grid.rows ? `${grid.rows} \xD7 ${grid.cols}` : t.table })
652
+ ] })
653
+ ] }),
654
+ state.table && /* @__PURE__ */ jsxs2(Fragment3, { children: [
655
+ tool(BetweenHorizontalStart, t.rowAbove, () => editor.chain().focus().addRowBefore().run()),
656
+ tool(BetweenHorizontalEnd, t.rowBelow, () => editor.chain().focus().addRowAfter().run()),
657
+ tool(BetweenVerticalStart, t.columnLeft, () => editor.chain().focus().addColumnBefore().run()),
658
+ tool(BetweenVerticalEnd, t.columnRight, () => editor.chain().focus().addColumnAfter().run()),
659
+ tool(PanelTop, t.headerRow, () => editor.chain().focus().toggleHeaderRow().run()),
660
+ docx && tool(TableCellsMerge, t.mergeCells, () => editor.chain().focus().mergeOrSplit().run()),
661
+ tool(TableRowsSplit, t.deleteRow, () => editor.chain().focus().deleteRow().run()),
662
+ tool(TableColumnsSplit, t.deleteColumn, () => editor.chain().focus().deleteColumn().run()),
663
+ tool(Trash2, t.deleteTable, () => editor.chain().focus().deleteTable().run())
664
+ ] }),
665
+ gap,
666
+ /* @__PURE__ */ jsxs2("span", { className: "w100n-text-anchor", children: [
667
+ /* @__PURE__ */ jsx2("button", { type: "button", "data-pop": true, "aria-expanded": pop === "image", "aria-label": t.image, title: t.image, onClick: () => {
668
+ setHref("");
669
+ pick("image");
670
+ }, children: /* @__PURE__ */ jsx2(ImagePlus, { "aria-hidden": true }) }),
671
+ pop === "image" && /* @__PURE__ */ jsxs2("span", { className: "w100n-text-pop w100n-text-form", children: [
672
+ /* @__PURE__ */ jsx2(
673
+ "input",
674
+ {
675
+ autoFocus: true,
676
+ value: href,
677
+ placeholder: t.imageLink,
678
+ "aria-label": t.imageLink,
679
+ onChange: (e) => setHref(e.target.value),
680
+ onKeyDown: (e) => {
681
+ if (e.key === "Enter" && href.trim()) {
682
+ editor.chain().focus().setImage({ src: href.trim() }).run();
683
+ setPop(null);
684
+ }
685
+ }
686
+ }
687
+ ),
688
+ /* @__PURE__ */ jsxs2("span", { className: "w100n-docs-row", children: [
689
+ docx && /* @__PURE__ */ jsxs2("label", { className: "w100n-pdf-link", children: [
690
+ t.chooseFile,
691
+ /* @__PURE__ */ jsx2("input", { type: "file", accept: "image/png,image/jpeg,image/gif", hidden: true, onChange: (e) => {
692
+ const file = e.target.files?.[0];
693
+ if (!file) return;
694
+ const reader = new FileReader();
695
+ reader.onload = () => {
696
+ editor.chain().focus().setImage({ src: String(reader.result), alt: file.name }).run();
697
+ setPop(null);
698
+ };
699
+ reader.readAsDataURL(file);
700
+ } })
701
+ ] }),
702
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-docs-action", disabled: !href.trim(), onClick: () => {
703
+ editor.chain().focus().setImage({ src: href.trim() }).run();
704
+ setPop(null);
705
+ }, children: [
706
+ /* @__PURE__ */ jsx2(ImagePlus, { "aria-hidden": true }),
707
+ t.put
708
+ ] })
709
+ ] })
710
+ ] })
711
+ ] }),
712
+ /* @__PURE__ */ jsxs2("span", { className: "w100n-text-anchor", children: [
713
+ /* @__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 }) }),
714
+ pop === "link" && /* @__PURE__ */ jsxs2("span", { className: "w100n-text-pop w100n-text-form", children: [
715
+ /* @__PURE__ */ jsx2("input", { autoFocus: true, value: href, placeholder: t.linkAddress, "aria-label": t.linkAddress, onChange: (e) => setHref(e.target.value), onKeyDown: (e) => {
716
+ if (e.key === "Enter") putLink();
717
+ } }),
718
+ /* @__PURE__ */ jsxs2("span", { className: "w100n-docs-row", children: [
719
+ state.link && /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-pdf-link", onClick: () => {
720
+ editor.chain().focus().extendMarkRange("link").unsetLink().run();
721
+ setPop(null);
722
+ }, children: [
723
+ /* @__PURE__ */ jsx2(Link2Off, { "aria-hidden": true }),
724
+ t.unlink
725
+ ] }),
726
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-docs-action", disabled: !href.trim(), onClick: putLink, children: [
727
+ /* @__PURE__ */ jsx2(Link, { "aria-hidden": true }),
728
+ t.apply
729
+ ] })
730
+ ] })
731
+ ] })
732
+ ] }),
733
+ docx && tool(SeparatorHorizontal, t.pageBreak, () => editor.chain().focus().insertContent({ type: "pageBreak" }).run()),
734
+ tool(Minus, t.rule, () => editor.chain().focus().setHorizontalRule().run())
735
+ ] });
736
+ else if (shown === "layout") tools = /* @__PURE__ */ jsxs2(Fragment3, { children: [
737
+ /* @__PURE__ */ jsx2("span", { className: "w100n-text-label", children: t.pageSize }),
738
+ [["A4", a4], ["Letter", letter]].map(([label, size], i) => /* @__PURE__ */ jsx2(
739
+ "button",
740
+ {
741
+ type: "button",
742
+ className: "w100n-text-word",
743
+ "aria-pressed": Math.abs(page.width - size.width) < 2 && Math.abs(page.height - size.height) < 2,
744
+ onClick: () => setLayout({ ...page, width: size.width, height: size.height }),
745
+ children: label
746
+ },
747
+ i
748
+ )),
749
+ gap,
750
+ /* @__PURE__ */ jsx2("span", { className: "w100n-text-label", children: t.margins }),
751
+ margins.map(([label, values], i) => /* @__PURE__ */ jsx2(
752
+ "button",
753
+ {
754
+ type: "button",
755
+ className: "w100n-text-word",
756
+ "aria-pressed": values.every((v, n) => Math.abs(page.margins[n] - v) < 2),
757
+ onClick: () => setLayout({ ...page, margins: values }),
758
+ children: label
759
+ },
760
+ i
761
+ ))
762
+ ] });
763
+ else if (shown === "refs") tools = /* @__PURE__ */ jsxs2(Fragment3, { children: [
764
+ state.toc < 0 ? /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-docs-action", onClick: () => {
765
+ const at = editor.state.selection.$from.before(1);
766
+ editor.chain().focus().insertContentAt(at, { type: "toc", attrs: { entries: JSON.stringify(entries(editor.state.doc, !docx, at - 1)) } }).run();
767
+ }, children: [
768
+ /* @__PURE__ */ jsx2(TableOfContents, { "aria-hidden": true }),
769
+ t.tocInsert
770
+ ] }) : /* @__PURE__ */ jsxs2(Fragment3, { children: [
771
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-text-word", onClick: () => editor.view.nodeDOM(state.toc)?.scrollIntoView({ behavior: "smooth", block: "start" }), children: [
772
+ /* @__PURE__ */ jsx2(TableOfContents, { "aria-hidden": true }),
773
+ t.tocGo
774
+ ] }),
775
+ /* @__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: [
776
+ /* @__PURE__ */ jsx2(Trash2, { "aria-hidden": true }),
777
+ t.tocRemove
778
+ ] })
779
+ ] }),
780
+ /* @__PURE__ */ jsx2("span", { className: "w100n-text-label", children: t.tocHint })
781
+ ] });
782
+ else if (shown === "review") tools = /* @__PURE__ */ jsxs2(Fragment3, { children: [
783
+ can.comment && /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-docs-action", disabled: state.empty, onClick: begin, children: [
784
+ /* @__PURE__ */ jsx2(MessageSquarePlus, { "aria-hidden": true }),
785
+ t.addComment
786
+ ] }),
787
+ docx && can.edit && /* @__PURE__ */ jsxs2(Fragment3, { children: [
788
+ /* @__PURE__ */ jsxs2(
789
+ "button",
790
+ {
791
+ type: "button",
792
+ className: "w100n-text-word",
793
+ "aria-pressed": state.suggest,
794
+ onClick: () => editor.view.dispatch(editor.state.tr.setMeta(suggesting, { on: !state.suggest, me: host?.me.name ?? "" })),
795
+ children: [
796
+ /* @__PURE__ */ jsx2(FilePenLine, { "aria-hidden": true }),
797
+ t.suggest
798
+ ]
799
+ }
800
+ ),
801
+ state.revs.length > 0 && /* @__PURE__ */ jsxs2(Fragment3, { children: [
802
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-docs-action", "data-tone": "green", onClick: () => decide(editor, true), children: [
803
+ /* @__PURE__ */ jsx2(CheckCheck, { "aria-hidden": true }),
804
+ t.acceptAll
805
+ ] }),
806
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-docs-action", "data-tone": "red", onClick: () => decide(editor, false), children: [
807
+ /* @__PURE__ */ jsx2(X, { "aria-hidden": true }),
808
+ t.rejectAll
809
+ ] })
810
+ ] })
811
+ ] }),
812
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-text-word", onClick: () => onPanel?.("comments"), children: [
813
+ /* @__PURE__ */ jsx2(MessagesSquare, { "aria-hidden": true }),
814
+ t.comments,
815
+ /* @__PURE__ */ jsx2("b", { children: open.length })
816
+ ] })
817
+ ] });
818
+ else tools = /* @__PURE__ */ jsxs2(Fragment3, { children: [
819
+ tool(Minus, t.zoomOut, () => zoomBy(-10)),
820
+ /* @__PURE__ */ jsxs2("span", { className: "w100n-pdf-zoom", children: [
821
+ percent,
822
+ "%"
823
+ ] }),
824
+ tool(Plus, t.zoomIn, () => zoomBy(10)),
825
+ docx && tool(MoveHorizontal, t.fitWidth, () => setZoom(null), zoom === null),
826
+ tool(Maximize2, t.fit, () => setZoom(100)),
827
+ gap,
828
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-text-word", onClick: () => onPanel?.("outline"), children: [
829
+ /* @__PURE__ */ jsx2(ListTree, { "aria-hidden": true }),
830
+ t.outline
831
+ ] })
832
+ ] });
833
+ 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;
834
+ return /* @__PURE__ */ jsxs2("div", { className: "w100n-text", "data-kind": kind, onKeyDown: keys, children: [
835
+ /* @__PURE__ */ jsxs2("div", { className: "w100n-text-ribbon", role: "toolbar", "aria-label": t.textTools, onKeyDown: rove, onPointerDown: (e) => {
836
+ if (e.target.closest("button")) e.preventDefault();
837
+ }, children: [
838
+ (tabs.length > 1 || live) && /* @__PURE__ */ jsxs2("div", { className: "w100n-text-tabs", children: [
839
+ /* @__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: () => {
840
+ setTab(key);
841
+ setPop(null);
842
+ }, children: label }, key)) }),
843
+ (can.edit || live) && /* @__PURE__ */ jsxs2("span", { className: "w100n-text-end", children: [
844
+ live && (live.ready ? !live.online : live.late || live.denied) && /* @__PURE__ */ jsxs2("span", { className: "w100n-text-live", title: live.denied ? void 0 : t.offlineHint, children: [
845
+ /* @__PURE__ */ jsx2(WifiOff, { "aria-hidden": true }),
846
+ live.denied ? t.liveOff : t.offline
847
+ ] }),
848
+ 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)) }),
849
+ state.suggest && /* @__PURE__ */ jsx2("span", { className: "w100n-docs-badge", children: t.suggesting }),
850
+ can.edit && /* @__PURE__ */ jsxs2(Fragment3, { children: [
851
+ tool(Undo2, t.undo, () => editor.chain().focus().undo().run(), void 0, !state.undo),
852
+ tool(Redo2, t.redo, () => editor.chain().focus().redo().run(), void 0, !state.redo),
853
+ saving === "failed" && /* @__PURE__ */ jsx2("span", { className: "w100n-docs-muted", children: t.saveFailed }),
854
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-docs-action", disabled: !dirty || saving === "busy" || !!live && !live.online, onClick: () => void save(), children: [
855
+ /* @__PURE__ */ jsx2(Save, { "aria-hidden": true }),
856
+ saving === "busy" ? t.saving : !dirty && saving === "done" ? t.saved : t.save
857
+ ] })
858
+ ] })
859
+ ] })
860
+ ] }),
861
+ /* @__PURE__ */ jsx2("div", { className: "w100n-text-tools", role: "group", "aria-label": tabs.find(([key]) => key === shown)?.[1], children: tools })
862
+ ] }),
863
+ /* @__PURE__ */ jsx2("div", { ref: scroller, className: "w100n-text-scroll", onClick: (e) => {
864
+ const id = e.target.closest("[data-note]")?.getAttribute("data-note");
865
+ if (!id) return;
866
+ setActive(id);
867
+ onPanel?.("comments");
868
+ }, 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 }) }) }) }),
869
+ slot && createPortal(/* @__PURE__ */ jsxs2("div", { className: "w100n-text-notes", children: [
870
+ draft && /* @__PURE__ */ jsxs2("div", { className: "w100n-text-card w100n-text-draft", children: [
871
+ /* @__PURE__ */ jsx2("blockquote", { children: draft.quote }),
872
+ /* @__PURE__ */ jsx2(
873
+ "textarea",
874
+ {
875
+ autoFocus: true,
876
+ value: draft.body,
877
+ placeholder: t.writeComment,
878
+ "aria-label": t.writeComment,
879
+ onChange: (e) => setDraft({ ...draft, body: e.target.value }),
880
+ onKeyDown: (e) => {
881
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) void post();
882
+ }
883
+ }
884
+ ),
885
+ /* @__PURE__ */ jsxs2("span", { className: "w100n-pdf-dialog-end", children: [
886
+ /* @__PURE__ */ jsx2("button", { type: "button", className: "w100n-pdf-link", onClick: drop, children: t.cancel }),
887
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-docs-action", disabled: !draft.body.trim() || posting === "busy", onClick: () => void post(), children: [
888
+ /* @__PURE__ */ jsx2(MessageSquarePlus, { "aria-hidden": true }),
889
+ t.post
890
+ ] })
891
+ ] })
892
+ ] }),
893
+ suggested.length > 0 && /* @__PURE__ */ jsxs2(Fragment3, { children: [
894
+ /* @__PURE__ */ jsxs2("p", { className: "w100n-docs-caption", children: [
895
+ /* @__PURE__ */ jsx2(FilePenLine, { "aria-hidden": true }),
896
+ t.suggestions,
897
+ /* @__PURE__ */ jsx2("span", { children: suggested.length })
898
+ ] }),
899
+ suggested.map((c, i) => /* @__PURE__ */ jsxs2("article", { className: "w100n-text-card w100n-text-change", "data-kind": c.kind, onClick: () => jump(c.from), children: [
900
+ /* @__PURE__ */ jsxs2("header", { children: [
901
+ /* @__PURE__ */ jsx2("span", { className: "w100n-text-face", "aria-hidden": true, children: (c.author || "?").slice(0, 1).toUpperCase() }),
902
+ /* @__PURE__ */ jsxs2("span", { className: "w100n-text-who", children: [
903
+ /* @__PURE__ */ jsx2("b", { children: c.author || t.someone }),
904
+ c.date && /* @__PURE__ */ jsx2("small", { children: when(c.date) })
905
+ ] })
906
+ ] }),
907
+ /* @__PURE__ */ jsxs2("p", { children: [
908
+ c.kind === "insertion" ? t.added : t.removed,
909
+ " ",
910
+ /* @__PURE__ */ jsx2("q", { children: c.text })
911
+ ] }),
912
+ can.edit && /* @__PURE__ */ jsxs2("span", { className: "w100n-text-actions", onClick: (e) => e.stopPropagation(), children: [
913
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-docs-action", "data-tone": "green", onClick: () => decide(editor, true, c.id), children: [
914
+ /* @__PURE__ */ jsx2(Check, { "aria-hidden": true }),
915
+ t.accept
916
+ ] }),
917
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-docs-action", "data-tone": "red", onClick: () => decide(editor, false, c.id), children: [
918
+ /* @__PURE__ */ jsx2(X, { "aria-hidden": true }),
919
+ t.reject
920
+ ] })
921
+ ] })
922
+ ] }, i))
923
+ ] }),
924
+ posting === "failed" && /* @__PURE__ */ jsx2("p", { className: "w100n-docs-muted", role: "alert", children: t.commentFailed }),
925
+ failed && /* @__PURE__ */ jsx2("p", { className: "w100n-docs-muted", role: "alert", children: t.changeFailed }),
926
+ /* @__PURE__ */ jsxs2("div", { className: "w100n-docs-row", children: [
927
+ /* @__PURE__ */ jsxs2("p", { className: "w100n-docs-caption", children: [
928
+ /* @__PURE__ */ jsx2(MessagesSquare, { "aria-hidden": true }),
929
+ t.comments,
930
+ /* @__PURE__ */ jsx2("span", { children: open.length })
931
+ ] }),
932
+ /* @__PURE__ */ jsx2("span", { className: "w100n-docs-chips", children: /* @__PURE__ */ jsx2("button", { type: "button", "aria-pressed": resolved, onClick: () => setResolved(!resolved), children: t.showResolved }) })
933
+ ] }),
934
+ !threads.length && !draft && /* @__PURE__ */ jsx2("p", { className: "w100n-docs-muted", children: can.comment ? t.commentHint : t.noComments }),
935
+ threads.map((c) => /* @__PURE__ */ jsxs2("article", { className: "w100n-text-card", "aria-current": active === c.id, "data-resolved": c.resolved, onClick: () => show(c), children: [
936
+ person(c),
937
+ c.quote && /* @__PURE__ */ jsx2("blockquote", { children: c.quote }),
938
+ words(c),
939
+ all.filter((r) => r.replyTo === c.id).map((r) => /* @__PURE__ */ jsxs2("div", { className: "w100n-text-reply", children: [
940
+ person(r),
941
+ words(r)
942
+ ] }, r.id)),
943
+ reply?.id === c.id && /* @__PURE__ */ jsxs2("div", { className: "w100n-text-reply", onClick: (e) => e.stopPropagation(), children: [
944
+ /* @__PURE__ */ jsx2(
945
+ "textarea",
946
+ {
947
+ autoFocus: true,
948
+ value: reply.body,
949
+ placeholder: t.writeComment,
950
+ "aria-label": t.reply,
951
+ onChange: (e) => setReply({ ...reply, body: e.target.value }),
952
+ onKeyDown: (e) => {
953
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) void answer(c);
954
+ }
955
+ }
956
+ ),
957
+ /* @__PURE__ */ jsxs2("span", { className: "w100n-pdf-dialog-end", children: [
958
+ /* @__PURE__ */ jsx2("button", { type: "button", className: "w100n-pdf-link", onClick: () => setReply(null), children: t.cancel }),
959
+ /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-docs-action", disabled: !reply.body.trim() || posting === "busy", onClick: () => void answer(c), children: [
960
+ /* @__PURE__ */ jsx2(Reply, { "aria-hidden": true }),
961
+ t.reply
962
+ ] })
963
+ ] })
964
+ ] }),
965
+ can.comment && reply?.id !== c.id && /* @__PURE__ */ jsxs2("span", { className: "w100n-text-actions", onClick: (e) => e.stopPropagation(), children: [
966
+ c.resolved && /* @__PURE__ */ jsx2("span", { className: "w100n-docs-badge", children: t.resolved }),
967
+ !c.resolved && /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-docs-action", onClick: () => setReply({ id: c.id, body: "" }), children: [
968
+ /* @__PURE__ */ jsx2(Reply, { "aria-hidden": true }),
969
+ t.reply
970
+ ] }),
971
+ host?.resolveComment && /* @__PURE__ */ jsxs2("button", { type: "button", className: "w100n-docs-action", "data-tone": "green", onClick: () => void settle(c), children: [
972
+ c.resolved ? /* @__PURE__ */ jsx2(RotateCcw, { "aria-hidden": true }) : /* @__PURE__ */ jsx2(Check, { "aria-hidden": true }),
973
+ c.resolved ? t.reopen : t.resolve
974
+ ] })
975
+ ] })
976
+ ] }, c.id))
977
+ ] }), slot)
978
+ ] });
979
+ }
980
+ export {
981
+ TextEditor as default
982
+ };
983
+ //# sourceMappingURL=TextEditor-4YETNX4T.js.map