@estiva-app/ui 0.72.0-widgets2.2 → 0.73.0-inline-text.1

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/CommentHighlights.d.ts +100 -0
  2. package/dist/CommentHighlights.d.ts.map +1 -0
  3. package/dist/ComposeBox.d.ts +55 -0
  4. package/dist/ComposeBox.d.ts.map +1 -0
  5. package/dist/ComposerTriggers.d.ts.map +1 -1
  6. package/dist/InlineText.d.ts +56 -0
  7. package/dist/InlineText.d.ts.map +1 -0
  8. package/dist/SelectionToolbar.d.ts +17 -1
  9. package/dist/SelectionToolbar.d.ts.map +1 -1
  10. package/dist/editor.d.ts +4 -1
  11. package/dist/editor.d.ts.map +1 -1
  12. package/dist/editor.js +748 -240
  13. package/dist/editor.js.map +4 -4
  14. package/dist/index.js.map +1 -1
  15. package/dist/looks.d.ts +6 -0
  16. package/dist/looks.d.ts.map +1 -1
  17. package/package.json +1 -5
  18. package/registry.json +403 -3
  19. package/src/CommentHighlights.mdx +61 -0
  20. package/src/CommentHighlights.stories.tsx +72 -0
  21. package/src/CommentHighlights.test.ts +100 -0
  22. package/src/CommentHighlights.ts +413 -0
  23. package/src/ComposeBox.mdx +58 -0
  24. package/src/ComposeBox.stories.tsx +81 -0
  25. package/src/ComposeBox.tsx +183 -0
  26. package/src/ComposerTriggers.tsx +9 -3
  27. package/src/InlineText.mdx +64 -0
  28. package/src/InlineText.stories.tsx +86 -0
  29. package/src/InlineText.test.tsx +187 -0
  30. package/src/InlineText.tsx +187 -0
  31. package/src/SelectionToolbar.actions.test.tsx +44 -0
  32. package/src/SelectionToolbar.mdx +1 -0
  33. package/src/SelectionToolbar.tsx +36 -2
  34. package/src/editor.ts +16 -1
  35. package/src/looks.ts +8 -0
  36. package/src/registry/app.test.ts +7 -1
  37. package/src/registry/registry.test.ts +6 -6
  38. package/stories/Choosing.mdx +3 -0
  39. package/dist/widgets-2/fixtures.d.ts +0 -102
  40. package/dist/widgets-2/fixtures.d.ts.map +0 -1
  41. package/dist/widgets-2/places.d.ts +0 -105
  42. package/dist/widgets-2/places.d.ts.map +0 -1
  43. package/dist/widgets-2/proposed.d.ts +0 -130
  44. package/dist/widgets-2/proposed.d.ts.map +0 -1
  45. package/dist/widgets-2.d.ts +0 -12
  46. package/dist/widgets-2.d.ts.map +0 -1
  47. package/dist/widgets-2.js +0 -585
  48. package/dist/widgets-2.js.map +0 -7
  49. package/src/widgets-2/fixtures.ts +0 -161
  50. package/src/widgets-2/places.tsx +0 -316
  51. package/src/widgets-2/proposed.tsx +0 -596
  52. package/src/widgets-2.ts +0 -11
  53. package/stories/widgets-2/ReadMe.mdx +0 -60
  54. package/stories/widgets-2/Widgets.stories.tsx +0 -504
  55. package/stories/widgets-2/today.tsx +0 -352
package/dist/editor.js CHANGED
@@ -7,7 +7,7 @@ import { IconBold, IconCode, IconItalic, IconLink, IconUnderline } from "@tabler
7
7
  import { Form } from "@estiva-app/ui";
8
8
  import { Popover } from "@estiva-app/ui";
9
9
  import { TextInput } from "@estiva-app/ui";
10
- import { Toolbar, ToolbarButton } from "@estiva-app/ui";
10
+ import { Toolbar, ToolbarButton, ToolbarSeparator } from "@estiva-app/ui";
11
11
  import { shortcutLabel } from "@estiva-app/ui";
12
12
  import { jsx, jsxs } from "react/jsx-runtime";
13
13
  var keptSelectionKey = new PluginKey("keptSelection");
@@ -83,7 +83,7 @@ var ICONS = {
83
83
  var SETTLE_MS = 250;
84
84
  var GAP = 8;
85
85
  var POPOVER_GAP = 4;
86
- function SelectionToolbar({ editor, marks = ["bold", "italic", "underline"], link = false, children }) {
86
+ function SelectionToolbar({ editor, marks = ["bold", "italic", "underline"], link = false, children, actions = [] }) {
87
87
  const [anchor, setAnchor] = useState(null);
88
88
  const [mode, setMode] = useState("marks");
89
89
  const [url, setUrl] = useState("");
@@ -274,131 +274,42 @@ function SelectionToolbar({ editor, marks = ["bold", "italic", "underline"], lin
274
274
  children: /* @__PURE__ */ jsx(IconLink, { size: 16, stroke: 1.5 })
275
275
  }
276
276
  ),
277
- children
277
+ children,
278
+ actions.length > 0 && /* @__PURE__ */ jsx(ToolbarSeparator, {}),
279
+ actions.map((action) => /* @__PURE__ */ jsx(
280
+ ToolbarButton,
281
+ {
282
+ "aria-label": action.label,
283
+ tooltip: action.label,
284
+ tooltipShortcut: action.keys ? shortcutLabel(action.keys) : void 0,
285
+ onMouseDown: (e) => e.preventDefault(),
286
+ onClick: () => {
287
+ hide();
288
+ action.run(editor);
289
+ },
290
+ children: action.icon
291
+ },
292
+ action.label
293
+ ))
278
294
  ] })
279
295
  )
280
296
  }
281
297
  );
282
298
  }
283
299
 
284
- // src/PastedImages.ts
300
+ // src/ComposeBox.tsx
301
+ import { useCallback as useCallback2, useEffect as useEffect2, useState as useState2 } from "react";
285
302
  import { Extension as Extension2 } from "@tiptap/core";
286
- import { Plugin as Plugin2, PluginKey as PluginKey2 } from "@tiptap/pm/state";
287
- var IMAGE_EXTENSIONS = {
288
- "image/png": "png",
289
- "image/jpeg": "jpg",
290
- "image/gif": "gif",
291
- "image/webp": "webp",
292
- "image/heic": "heic",
293
- "image/heif": "heif",
294
- "image/svg+xml": "svg"
295
- };
296
- var GENERIC_STEMS = /* @__PURE__ */ new Set(["image", "blob", "unknown", "untitled", "screenshot"]);
297
- function extensionFor(contentType) {
298
- const type = contentType.toLowerCase();
299
- return IMAGE_EXTENSIONS[type] ?? type.split("/")[1]?.split("+")[0] ?? "png";
300
- }
301
- function pastedImageName(contentType, at = /* @__PURE__ */ new Date(), index = 0) {
302
- const pad = (n) => String(n).padStart(2, "0");
303
- const stamp = `${at.getFullYear()}-${pad(at.getMonth() + 1)}-${pad(at.getDate())} ${pad(at.getHours())}-${pad(at.getMinutes())}-${pad(at.getSeconds())}`;
304
- const nth = index > 0 ? ` (${index + 1})` : "";
305
- return `Screenshot ${stamp}${nth}.${extensionFor(contentType)}`;
306
- }
307
- function hasUsableName(name) {
308
- const dot = name.lastIndexOf(".");
309
- if (dot <= 0 || dot === name.length - 1) return false;
310
- return !GENERIC_STEMS.has(name.slice(0, dot).toLowerCase());
311
- }
312
- function pastedImages(source, at = /* @__PURE__ */ new Date()) {
313
- if (!source) return [];
314
- if (source.getData("text/plain").trim() !== "") return [];
315
- const images = Array.from(source.files ?? []).filter((f) => f.type.toLowerCase().startsWith("image/"));
316
- let generated = 0;
317
- return images.map(
318
- (file) => hasUsableName(file.name) ? file : new File([file], pastedImageName(file.type, at, generated++), { type: file.type, lastModified: file.lastModified })
319
- );
320
- }
321
- var PastedImages = Extension2.create({
322
- name: "pastedImages",
323
- addOptions() {
324
- return { onImages: () => false };
325
- },
326
- addProseMirrorPlugins() {
327
- const options = this.options;
328
- return [
329
- new Plugin2({
330
- key: new PluginKey2("pastedImages"),
331
- props: {
332
- handlePaste: (_view, event) => {
333
- const files = pastedImages(event.clipboardData);
334
- return files.length > 0 && options.onImages(files);
335
- }
336
- }
337
- })
338
- ];
339
- }
340
- });
341
-
342
- // src/PastedMarkdown.ts
343
- import { Extension as Extension3 } from "@tiptap/core";
344
- import { Slice } from "@tiptap/pm/model";
345
- import { Plugin as Plugin3, PluginKey as PluginKey3 } from "@tiptap/pm/state";
346
- function inTableCell($context) {
347
- for (let depth = $context.depth; depth > 0; depth--) {
348
- const role = $context.node(depth).type.spec.tableRole;
349
- if (role === "cell" || role === "header_cell") return true;
350
- }
351
- return false;
352
- }
353
- var hasTable = (blocks) => blocks.some((block) => block.type === "table" || hasTable(block.content ?? []));
354
- var PastedMarkdown = Extension3.create({
355
- name: "pastedMarkdown",
356
- addOptions() {
357
- return { read: () => void 0 };
358
- },
359
- addProseMirrorPlugins() {
360
- const options = this.options;
361
- return [
362
- new Plugin3({
363
- key: new PluginKey3("pastedMarkdown"),
364
- props: {
365
- // Reached only for a text paste outside code; `plain` is the Shift paste.
366
- clipboardTextParser: (text, $context, plain, view) => {
367
- if (plain) return void 0;
368
- try {
369
- const blocks = options.read(text.replace(/\r\n?/g, "\n"))?.content ?? [];
370
- if (blocks.every((block) => block.type === "paragraph")) return void 0;
371
- if (inTableCell($context) && hasTable(blocks)) return void 0;
372
- return Slice.maxOpen(view.state.schema.nodeFromJSON({ type: "doc", content: blocks }).content);
373
- } catch {
374
- return void 0;
375
- }
376
- }
377
- }
378
- })
379
- ];
380
- }
381
- });
382
-
383
- // src/CodeBlockColours.ts
384
- import { CodeBlockLowlight } from "@tiptap/extension-code-block-lowlight";
385
-
386
- // src/codeColours.ts
387
- import { createElement } from "react";
388
- import { common, createLowlight } from "lowlight";
389
- var instance;
390
- function lowlight() {
391
- return instance ??= createLowlight(common);
392
- }
303
+ import { EditorContent } from "@tiptap/react";
304
+ import { IconArrowUp } from "@tabler/icons-react";
305
+ import { IconButton } from "@estiva-app/ui";
306
+ import { Toolbar as Toolbar2 } from "@estiva-app/ui";
307
+ import { cn } from "@estiva-app/ui";
308
+ import { richTextClassName } from "@estiva-app/ui";
393
309
 
394
- // src/CodeBlockColours.ts
395
- var namedOnly = {
396
- highlight: (language, value) => lowlight().highlight(language, value),
397
- highlightAuto: (value) => ({ type: "root", children: [{ type: "text", value }], data: { language: void 0, relevance: 0 } }),
398
- listLanguages: () => lowlight().listLanguages(),
399
- registered: (language) => lowlight().registered(language)
400
- };
401
- var CodeBlockColours = CodeBlockLowlight.configure({ lowlight: namedOnly });
310
+ // src/looks.ts
311
+ var FLOATING_SURFACE_CLASSES = "rounded-lg border border-border-default bg-bg-elevated shadow-lg";
312
+ var WRITING_BOX_CLASSES = "relative bg-bg-inset border border-border-default rounded-lg p-3 flex flex-col gap-4 signal:transition-shadow signal:focus-within:border-border-focus signal:focus-within:shadow-focus-ring";
402
313
 
403
314
  // src/suggestionPopup.tsx
404
315
  import { forwardRef, useRef as useRef2, useImperativeHandle } from "react";
@@ -512,6 +423,592 @@ function suggestionPopup(options) {
512
423
  };
513
424
  }
514
425
 
426
+ // src/ComposeBox.tsx
427
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
428
+ var COMPOSE_EDITOR_CLASSES = richTextClassName("default", "outline-none w-full bg-transparent min-h-[20px] [&>p+p]:-mt-1");
429
+ var ComposeKeys = Extension2.create({
430
+ name: "composeKeys",
431
+ addStorage() {
432
+ return { send: null };
433
+ },
434
+ addKeyboardShortcuts() {
435
+ return {
436
+ Enter: () => {
437
+ if (isSuggestionActive()) return false;
438
+ this.storage.send?.();
439
+ return true;
440
+ },
441
+ "Shift-Enter": ({ editor }) => {
442
+ const { $from } = editor.state.selection;
443
+ for (let d = $from.depth; d > 0; d--) {
444
+ const node = $from.node(d);
445
+ if (node.type.name === "listItem") {
446
+ return node.textContent.length === 0 ? editor.commands.liftListItem("listItem") : editor.commands.splitListItem("listItem");
447
+ }
448
+ if (node.type.name === "blockquote") {
449
+ if ($from.parent.textContent.length === 0) return editor.commands.lift("paragraph");
450
+ break;
451
+ }
452
+ }
453
+ return editor.commands.splitBlock();
454
+ }
455
+ };
456
+ }
457
+ });
458
+ function isEmptyDoc(editor) {
459
+ const doc2 = editor.state.doc;
460
+ if (doc2.textContent.length > 0) return false;
461
+ let empty = true;
462
+ doc2.descendants((node) => {
463
+ if (node.isAtom && node.type.name !== "hardBreak") empty = false;
464
+ });
465
+ return empty;
466
+ }
467
+ function ComposeBox({ editor, onSend, placeholder, tools, attachments, ready = false, holding = false, marked = false, selection, className }) {
468
+ const [empty, setEmpty] = useState2(true);
469
+ const canSend = (!empty || ready) && !holding;
470
+ const send = useCallback2(() => {
471
+ if (!editor || holding || isEmptyDoc(editor) && !ready) return true;
472
+ if (onSend(editor) === false) return true;
473
+ editor.commands.clearContent(true);
474
+ editor.commands.focus();
475
+ return true;
476
+ }, [editor, holding, ready, onSend]);
477
+ useEffect2(() => {
478
+ if (!editor) return;
479
+ const storage = editor.storage.composeKeys;
480
+ if (!storage) return;
481
+ storage.send = send;
482
+ return () => {
483
+ storage.send = null;
484
+ };
485
+ }, [editor, send]);
486
+ useEffect2(() => {
487
+ if (!editor) return;
488
+ const read = () => setEmpty(isEmptyDoc(editor));
489
+ read();
490
+ editor.on("update", read);
491
+ return () => {
492
+ editor.off("update", read);
493
+ };
494
+ }, [editor]);
495
+ return /* @__PURE__ */ jsx3("div", { className: cn("relative", className), children: /* @__PURE__ */ jsxs2("div", { className: cn(WRITING_BOX_CLASSES, "focus-within:border-border-strong transition-colors"), children: [
496
+ /* @__PURE__ */ jsxs2("div", { "data-composer-editor": true, className: cn("relative min-h-[20px] transition-all", marked && "border-l-4 border-border-strong pl-2"), children: [
497
+ /* @__PURE__ */ jsx3(EditorContent, { editor }),
498
+ /* @__PURE__ */ jsx3(SelectionToolbar, { editor, ...selection }),
499
+ empty && /* @__PURE__ */ jsx3("div", { className: "pointer-events-none absolute inset-0 flex flex-wrap items-center gap-1 text-body-2 text-text-muted", children: placeholder })
500
+ ] }),
501
+ attachments && /* @__PURE__ */ jsx3("div", { className: "flex flex-wrap gap-2", children: attachments }),
502
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between", children: [
503
+ tools ? /* @__PURE__ */ jsx3(Toolbar2, { "aria-label": "Compose tools", surface: false, className: "flex items-center gap-1", children: tools }) : /* @__PURE__ */ jsx3("span", {}),
504
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2.5", children: [
505
+ /* @__PURE__ */ jsx3("span", { className: "hidden select-none font-mono text-menu font-normal tracking-wider text-text-muted signal:inline", children: "Enter to send \xB7 Shift+Enter new line" }),
506
+ /* @__PURE__ */ jsx3(
507
+ IconButton,
508
+ {
509
+ variant: "primary",
510
+ onMouseDown: (e) => {
511
+ e.preventDefault();
512
+ send();
513
+ },
514
+ onClick: (e) => {
515
+ if (e.detail === 0) send();
516
+ },
517
+ disabled: !canSend,
518
+ "aria-label": "Send",
519
+ tooltip: "Send",
520
+ tooltipShortcut: "Enter",
521
+ glow: canSend,
522
+ children: /* @__PURE__ */ jsx3(IconArrowUp, { size: 16, stroke: 1.5 })
523
+ }
524
+ )
525
+ ] })
526
+ ] })
527
+ ] }) });
528
+ }
529
+
530
+ // src/InlineText.tsx
531
+ import { useCallback as useCallback3, useEffect as useEffect3, useImperativeHandle as useImperativeHandle2, useRef as useRef3, useState as useState3 } from "react";
532
+ import { Extension as Extension3, Node as Node2 } from "@tiptap/core";
533
+ import { history, redo, undo } from "@tiptap/pm/history";
534
+ import { EditorContent as EditorContent2, useEditor } from "@tiptap/react";
535
+ import { cn as cn2 } from "@estiva-app/ui";
536
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
537
+ var VARIANT = {
538
+ title: "text-h2 text-text-primary",
539
+ summary: "text-body-1 text-text-secondary"
540
+ };
541
+ var ROOM = "-mx-2 px-2 py-1";
542
+ var OneLine = Node2.create({ name: "doc", topNode: true, content: "paragraph" });
543
+ var Paragraph = Node2.create({
544
+ name: "paragraph",
545
+ content: "text*",
546
+ parseHTML: () => [{ tag: "p" }],
547
+ renderHTML: () => ["p", 0]
548
+ });
549
+ var Text = Node2.create({ name: "text" });
550
+ var Undo = Extension3.create({
551
+ name: "inlineTextUndo",
552
+ addProseMirrorPlugins: () => [history()],
553
+ addKeyboardShortcuts() {
554
+ const run = (command) => () => command(this.editor.state, this.editor.view.dispatch);
555
+ return { "Mod-z": run(undo), "Shift-Mod-z": run(redo), "Mod-y": run(redo) };
556
+ }
557
+ });
558
+ var doc = (text) => ({ type: "doc", content: [{ type: "paragraph", content: text ? [{ type: "text", text }] : [] }] });
559
+ var oneLine = (text) => text.replace(/\s+/g, " ").trim();
560
+ function InlineText({ value, placeholder, label, onCommit, onText, onEnter, variant = "title", clearable = false, readOnly = false, className, ref }) {
561
+ const saved = useRef3(value);
562
+ const [empty, setEmpty] = useState3(!value);
563
+ const latest = useRef3({ onEnter, onText, onCommit, clearable });
564
+ latest.current = { onEnter, onText, onCommit, clearable };
565
+ const save = useCallback3(async (text) => {
566
+ const next = oneLine(text);
567
+ if (next === saved.current) return;
568
+ if (!next && !latest.current.clearable) return;
569
+ if (await latest.current.onCommit(next)) saved.current = next;
570
+ }, []);
571
+ const editor = useEditor({
572
+ extensions: [OneLine, Paragraph, Text, Undo],
573
+ content: doc(value),
574
+ editable: !readOnly,
575
+ editorProps: {
576
+ attributes: {
577
+ "aria-label": label,
578
+ role: "textbox",
579
+ "aria-multiline": "false",
580
+ "aria-placeholder": placeholder,
581
+ ...readOnly ? { "aria-readonly": "true" } : {},
582
+ class: "outline-none"
583
+ },
584
+ handleKeyDown: (view, event) => {
585
+ if (event.key === "Enter") {
586
+ event.preventDefault();
587
+ void save(view.state.doc.textContent);
588
+ latest.current.onEnter?.();
589
+ return true;
590
+ }
591
+ if (event.key === "Escape") {
592
+ view.dom.blur();
593
+ return true;
594
+ }
595
+ return false;
596
+ },
597
+ // A paste is its plain text on this one line, whatever it was copied from.
598
+ handlePaste: (view, event) => {
599
+ const text = event.clipboardData?.getData("text/plain");
600
+ if (text == null) return false;
601
+ view.dispatch(view.state.tr.insertText(text.replace(/\s+/g, " ")));
602
+ return true;
603
+ }
604
+ },
605
+ onFocus: ({ editor: e }) => latest.current.onText?.(e.getText()),
606
+ onUpdate: ({ editor: e }) => {
607
+ setEmpty(e.isEmpty);
608
+ latest.current.onText?.(e.getText());
609
+ },
610
+ onBlur: ({ editor: e }) => {
611
+ latest.current.onText?.(null);
612
+ if (e.isEmpty && saved.current && !latest.current.clearable) {
613
+ e.commands.setContent(doc(saved.current), { emitUpdate: false });
614
+ setEmpty(false);
615
+ return;
616
+ }
617
+ void save(e.getText());
618
+ }
619
+ }, [readOnly]);
620
+ useEffect3(() => {
621
+ if (!editor || editor.isDestroyed || value === saved.current || editor.isFocused) return;
622
+ if (oneLine(editor.getText()) !== saved.current) return;
623
+ editor.commands.setContent(doc(value), { emitUpdate: false });
624
+ saved.current = value;
625
+ setEmpty(!value);
626
+ }, [editor, value]);
627
+ useImperativeHandle2(ref, () => ({
628
+ focus: (at = "start") => editor?.commands.focus(at)
629
+ }), [editor]);
630
+ return /* @__PURE__ */ jsxs3("div", { className: cn2("relative", VARIANT[variant], className), children: [
631
+ /* @__PURE__ */ jsx4(EditorContent2, { editor, className: cn2(ROOM, !readOnly && "cursor-text") }),
632
+ empty && !readOnly && /* @__PURE__ */ jsx4("span", { "aria-hidden": true, className: "pointer-events-none absolute left-0 top-1 text-text-muted", children: placeholder })
633
+ ] });
634
+ }
635
+
636
+ // src/CommentHighlights.ts
637
+ import { Extension as Extension4 } from "@tiptap/core";
638
+ import { Plugin as Plugin2, PluginKey as PluginKey2 } from "@tiptap/pm/state";
639
+ import { Decoration as Decoration2, DecorationSet as DecorationSet2 } from "@tiptap/pm/view";
640
+ var commentHighlightsKey = new PluginKey2("commentHighlights");
641
+ var COMMENT_HIGHLIGHT_CLASSES = {
642
+ rest: "cursor-pointer rounded-sm bg-warning-muted",
643
+ active: "cursor-pointer rounded-sm bg-warning-muted underline decoration-warning-default decoration-2 underline-offset-4"
644
+ };
645
+ var QUOTE_WHOLE_MAX = 1e3;
646
+ var QUOTE_END_LENGTH = 150;
647
+ var QUOTE_CONTEXT_LENGTH = 32;
648
+ function placesIn(doc2) {
649
+ const places = /* @__PURE__ */ new Map();
650
+ const walk = (parent, parentPos) => {
651
+ parent.forEach((node, offset, index) => {
652
+ const pos = parentPos + offset;
653
+ const id = node.attrs.blockId;
654
+ if (typeof id === "string" && id && !places.has(id)) places.set(id, { node, pos, parent: parentPos, index });
655
+ if (!node.isTextblock && !node.isLeaf) walk(node, pos + 1);
656
+ });
657
+ };
658
+ walk(doc2, 0);
659
+ return places;
660
+ }
661
+ function textOf(block) {
662
+ let text = "";
663
+ const starts = [];
664
+ const ends = [];
665
+ block.node.descendants((node, offset) => {
666
+ const at = block.pos + 1 + offset;
667
+ if (node.isText && node.text) {
668
+ for (let i = 0; i < node.text.length; i++) starts.push(at + i), ends.push(at + i + 1);
669
+ text += node.text;
670
+ } else if (node.isInline && node.isLeaf) {
671
+ const wire = typeof node.attrs.uri === "string" ? node.attrs.uri : "";
672
+ for (let i = 0; i < wire.length; i++) starts.push(at), ends.push(at + node.nodeSize);
673
+ text += wire;
674
+ }
675
+ return true;
676
+ });
677
+ return { text, starts, ends };
678
+ }
679
+ function find(text, words, from, context) {
680
+ let first = -1;
681
+ for (let at = text.indexOf(words, from); at !== -1; at = text.indexOf(words, at + 1)) {
682
+ if (first === -1) first = at;
683
+ if (context(at)) return at;
684
+ }
685
+ return first;
686
+ }
687
+ function runIn(doc2, places, ids) {
688
+ const present = ids.map((id) => places.get(id)).filter((p) => !!p);
689
+ if (present.length === 0) return null;
690
+ const byParent = /* @__PURE__ */ new Map();
691
+ for (const p of present) byParent.set(p.parent, [...byParent.get(p.parent) ?? [], p]);
692
+ const group = [...byParent.values()].reduce((best, one) => one.length > best.length ? one : best);
693
+ const low = Math.min(...group.map((p) => p.index));
694
+ const high = Math.max(...group.map((p) => p.index));
695
+ const contentStart = group[0].parent;
696
+ const parent = contentStart === 0 ? doc2 : doc2.nodeAt(contentStart - 1);
697
+ if (!parent) return group;
698
+ const run = [];
699
+ parent.forEach((node, offset, index) => {
700
+ if (index >= low && index <= high) run.push({ node, pos: contentStart + offset, parent: contentStart, index });
701
+ });
702
+ return run;
703
+ }
704
+ function placeOf(doc2, anchor, places = placesIn(doc2)) {
705
+ const run = runIn(doc2, places, anchor.blocks);
706
+ if (!run) return { state: "detached" };
707
+ const from = run[0].pos;
708
+ const to = run[run.length - 1].pos + run[run.length - 1].node.nodeSize;
709
+ const { quote } = anchor;
710
+ if (!quote) return { state: "resolved", from, to, words: false };
711
+ const reworded = { state: "reworded", from, to };
712
+ const firstId = anchor.blocks[0];
713
+ const lastId = anchor.blocks[anchor.blocks.length - 1];
714
+ const first = run.findIndex((p) => p.node.attrs.blockId === firstId);
715
+ const last = run.findIndex((p) => p.node.attrs.blockId === lastId);
716
+ if (first === -1 || last === -1 || last < first) return reworded;
717
+ const a = textOf(run[first]);
718
+ const b = first === last ? a : textOf(run[last]);
719
+ const prefix = quote.prefix && quote.prefix.length <= QUOTE_CONTEXT_LENGTH ? quote.prefix : void 0;
720
+ const suffix = quote.suffix && quote.suffix.length <= QUOTE_CONTEXT_LENGTH ? quote.suffix : void 0;
721
+ const before = (text, i) => !prefix || text.slice(Math.max(0, i - prefix.length), i) === prefix;
722
+ const after = (text, i) => !suffix || text.slice(i, i + suffix.length) === suffix;
723
+ if (quote.end === void 0) {
724
+ const at = find(a.text, quote.start, 0, (i) => before(a.text, i) && after(a.text, i + quote.start.length));
725
+ if (at === -1 || quote.start === "") return reworded;
726
+ return { state: "resolved", from: a.starts[at], to: a.ends[at + quote.start.length - 1], words: true };
727
+ }
728
+ const startAt = find(a.text, quote.start, 0, (i) => before(a.text, i));
729
+ if (startAt === -1 || quote.start === "") return reworded;
730
+ const endFrom = first === last ? startAt + quote.start.length : 0;
731
+ const endAt = find(b.text, quote.end, endFrom, (i) => after(b.text, i + quote.end.length));
732
+ if (endAt === -1 || quote.end === "") return reworded;
733
+ return { state: "resolved", from: a.starts[startAt], to: b.ends[endAt + quote.end.length - 1], words: true };
734
+ }
735
+ var CommentHighlights = Extension4.create({
736
+ name: "commentHighlights",
737
+ addOptions() {
738
+ return { onSelect: null };
739
+ },
740
+ addProseMirrorPlugins() {
741
+ const options = this.options;
742
+ const editor = this.editor;
743
+ return [
744
+ new Plugin2({
745
+ key: commentHighlightsKey,
746
+ state: {
747
+ init: () => ({ anchors: [], active: null }),
748
+ apply(tr, previous) {
749
+ const meta = tr.getMeta(commentHighlightsKey);
750
+ return meta ?? previous;
751
+ }
752
+ },
753
+ props: {
754
+ decorations(state) {
755
+ const { anchors, active } = commentHighlightsKey.getState(state) ?? { anchors: [], active: null };
756
+ const places = placesIn(state.doc);
757
+ const drawn = anchors.flatMap((anchor) => {
758
+ const place2 = placeOf(state.doc, anchor, places);
759
+ if (place2.state !== "resolved") return [];
760
+ const attrs = { class: anchor.id === active ? COMMENT_HIGHLIGHT_CLASSES.active : COMMENT_HIGHLIGHT_CLASSES.rest, "data-comment-id": anchor.id };
761
+ if (place2.words) return [Decoration2.inline(place2.from, place2.to, attrs)];
762
+ const blocks = [];
763
+ state.doc.nodesBetween(place2.from, place2.to, (node, pos) => {
764
+ if (pos < place2.from || pos + node.nodeSize > place2.to) return true;
765
+ blocks.push(Decoration2.node(pos, pos + node.nodeSize, attrs));
766
+ return false;
767
+ });
768
+ return blocks;
769
+ });
770
+ return drawn.length ? DecorationSet2.create(state.doc, drawn) : null;
771
+ },
772
+ handleClick(view, pos) {
773
+ const { anchors } = commentHighlightsKey.getState(view.state) ?? { anchors: [] };
774
+ const places = placesIn(view.state.doc);
775
+ let hit = null;
776
+ for (const anchor of anchors) {
777
+ const place2 = placeOf(view.state.doc, anchor, places);
778
+ if (place2.state !== "resolved" || pos < place2.from || pos >= place2.to) continue;
779
+ const size = place2.to - place2.from;
780
+ if (!hit || size < hit.size) hit = { id: anchor.id, size };
781
+ }
782
+ if (hit) {
783
+ options.onSelect?.(hit.id);
784
+ editor.emit("commentSelect", { id: hit.id });
785
+ }
786
+ return false;
787
+ }
788
+ }
789
+ })
790
+ ];
791
+ }
792
+ });
793
+ function setCommentAnchors(editor, anchors, active = null) {
794
+ if (editor.isDestroyed) return;
795
+ editor.view.dispatch(editor.state.tr.setMeta(commentHighlightsKey, { anchors, active }));
796
+ }
797
+ function commentPlace(editor, anchor) {
798
+ return placeOf(editor.state.doc, anchor);
799
+ }
800
+ function commentElement(editor, anchor) {
801
+ const drawn = editor.view.dom.querySelector(`[data-comment-id="${CSS.escape(anchor.id)}"]`);
802
+ if (drawn) return drawn;
803
+ const place2 = placeOf(editor.state.doc, anchor);
804
+ if (place2.state === "detached") return null;
805
+ const dom = editor.view.nodeDOM(place2.from);
806
+ return dom instanceof HTMLElement ? dom : null;
807
+ }
808
+ function quoteText(quote) {
809
+ return quote.end ? `${quote.start} \u2026 ${quote.end}` : quote.start;
810
+ }
811
+ function headOf(text, length) {
812
+ if (text.length <= length) return text;
813
+ const cut = text.lastIndexOf(" ", length);
814
+ return text.slice(0, cut > length / 2 ? cut : length);
815
+ }
816
+ function tailOf(text, length) {
817
+ if (text.length <= length) return text;
818
+ const from = text.length - length;
819
+ const cut = text.indexOf(" ", from);
820
+ return text.slice(cut !== -1 && cut < from + length / 2 ? cut + 1 : from);
821
+ }
822
+ function occurrences(text, words) {
823
+ let count = 0;
824
+ for (let at = text.indexOf(words); words && at !== -1; at = text.indexOf(words, at + 1)) count++;
825
+ return count;
826
+ }
827
+ function offsetIn(block, pos) {
828
+ const at = block.starts.findIndex((start) => start >= pos);
829
+ return at === -1 ? block.starts.length : at;
830
+ }
831
+ function anchorOfSelection(editor) {
832
+ const { doc: doc2, selection } = editor.state;
833
+ if (selection.empty) return null;
834
+ const { $from, $to } = selection;
835
+ const places = placesIn(doc2);
836
+ const around = (depth) => {
837
+ for (let d = depth; d > 0; d--) {
838
+ const id = $from.node(d).attrs.blockId;
839
+ if (typeof id === "string" && id && places.has(id)) return places.get(id);
840
+ }
841
+ return void 0;
842
+ };
843
+ let run;
844
+ if ($from.parent === $to.parent && $from.parent.isTextblock) {
845
+ const found = around($from.depth);
846
+ if (!found) return null;
847
+ run = [found];
848
+ } else {
849
+ const depth = $from.sharedDepth($to.pos);
850
+ const low = $from.index(depth);
851
+ const high = $to.pos >= $to.end(depth) ? $from.node(depth).childCount - 1 : $to.index(depth);
852
+ run = [];
853
+ for (let i = low; i <= high; i++) {
854
+ const id = $from.node(depth).child(i).attrs.blockId;
855
+ const place2 = typeof id === "string" ? places.get(id) : void 0;
856
+ if (!place2) {
857
+ const found = around(depth);
858
+ if (!found) return null;
859
+ run = [found];
860
+ break;
861
+ }
862
+ run.push(place2);
863
+ }
864
+ }
865
+ let firstText = textOf(run[0]);
866
+ let lastText = run.length === 1 ? firstText : textOf(run[run.length - 1]);
867
+ let start = offsetIn(firstText, selection.from);
868
+ let end = offsetIn(lastText, selection.to);
869
+ if (run.length > 1 && start >= firstText.text.length) run = run.slice(1), firstText = textOf(run[0]), start = 0;
870
+ if (run.length > 1 && end <= 0) run = run.slice(0, -1), lastText = textOf(run[run.length - 1]), end = lastText.text.length;
871
+ if (run.length === 1) lastText = firstText;
872
+ if (run.length === 1 && firstText.text && start >= end) return null;
873
+ const blocks = run.map((p) => p.node.attrs.blockId);
874
+ if (start === 0 && end >= lastText.text.length) return { blocks };
875
+ const single = run.length === 1;
876
+ const quote = { start: "" };
877
+ if (single && end - start <= QUOTE_WHOLE_MAX) {
878
+ quote.start = firstText.text.slice(start, end);
879
+ } else {
880
+ const startWords = single ? firstText.text.slice(start, end) : firstText.text.slice(start);
881
+ const endWords = single ? startWords : lastText.text.slice(0, end);
882
+ quote.start = headOf(startWords, QUOTE_END_LENGTH);
883
+ quote.end = tailOf(endWords, QUOTE_END_LENGTH);
884
+ }
885
+ if (!quote.start || quote.end === "") return { blocks };
886
+ const prefix = firstText.text.slice(Math.max(0, start - QUOTE_CONTEXT_LENGTH), start);
887
+ const suffix = lastText.text.slice(end, end + QUOTE_CONTEXT_LENGTH);
888
+ if (prefix && occurrences(firstText.text, quote.start) > 1) quote.prefix = prefix;
889
+ if (suffix && occurrences(lastText.text, quote.end ?? quote.start) > 1) quote.suffix = suffix;
890
+ return { blocks, quote };
891
+ }
892
+
893
+ // src/PastedImages.ts
894
+ import { Extension as Extension5 } from "@tiptap/core";
895
+ import { Plugin as Plugin3, PluginKey as PluginKey3 } from "@tiptap/pm/state";
896
+ var IMAGE_EXTENSIONS = {
897
+ "image/png": "png",
898
+ "image/jpeg": "jpg",
899
+ "image/gif": "gif",
900
+ "image/webp": "webp",
901
+ "image/heic": "heic",
902
+ "image/heif": "heif",
903
+ "image/svg+xml": "svg"
904
+ };
905
+ var GENERIC_STEMS = /* @__PURE__ */ new Set(["image", "blob", "unknown", "untitled", "screenshot"]);
906
+ function extensionFor(contentType) {
907
+ const type = contentType.toLowerCase();
908
+ return IMAGE_EXTENSIONS[type] ?? type.split("/")[1]?.split("+")[0] ?? "png";
909
+ }
910
+ function pastedImageName(contentType, at = /* @__PURE__ */ new Date(), index = 0) {
911
+ const pad = (n) => String(n).padStart(2, "0");
912
+ const stamp = `${at.getFullYear()}-${pad(at.getMonth() + 1)}-${pad(at.getDate())} ${pad(at.getHours())}-${pad(at.getMinutes())}-${pad(at.getSeconds())}`;
913
+ const nth = index > 0 ? ` (${index + 1})` : "";
914
+ return `Screenshot ${stamp}${nth}.${extensionFor(contentType)}`;
915
+ }
916
+ function hasUsableName(name) {
917
+ const dot = name.lastIndexOf(".");
918
+ if (dot <= 0 || dot === name.length - 1) return false;
919
+ return !GENERIC_STEMS.has(name.slice(0, dot).toLowerCase());
920
+ }
921
+ function pastedImages(source, at = /* @__PURE__ */ new Date()) {
922
+ if (!source) return [];
923
+ if (source.getData("text/plain").trim() !== "") return [];
924
+ const images = Array.from(source.files ?? []).filter((f) => f.type.toLowerCase().startsWith("image/"));
925
+ let generated = 0;
926
+ return images.map(
927
+ (file) => hasUsableName(file.name) ? file : new File([file], pastedImageName(file.type, at, generated++), { type: file.type, lastModified: file.lastModified })
928
+ );
929
+ }
930
+ var PastedImages = Extension5.create({
931
+ name: "pastedImages",
932
+ addOptions() {
933
+ return { onImages: () => false };
934
+ },
935
+ addProseMirrorPlugins() {
936
+ const options = this.options;
937
+ return [
938
+ new Plugin3({
939
+ key: new PluginKey3("pastedImages"),
940
+ props: {
941
+ handlePaste: (_view, event) => {
942
+ const files = pastedImages(event.clipboardData);
943
+ return files.length > 0 && options.onImages(files);
944
+ }
945
+ }
946
+ })
947
+ ];
948
+ }
949
+ });
950
+
951
+ // src/PastedMarkdown.ts
952
+ import { Extension as Extension6 } from "@tiptap/core";
953
+ import { Slice } from "@tiptap/pm/model";
954
+ import { Plugin as Plugin4, PluginKey as PluginKey4 } from "@tiptap/pm/state";
955
+ function inTableCell($context) {
956
+ for (let depth = $context.depth; depth > 0; depth--) {
957
+ const role = $context.node(depth).type.spec.tableRole;
958
+ if (role === "cell" || role === "header_cell") return true;
959
+ }
960
+ return false;
961
+ }
962
+ var hasTable = (blocks) => blocks.some((block) => block.type === "table" || hasTable(block.content ?? []));
963
+ var PastedMarkdown = Extension6.create({
964
+ name: "pastedMarkdown",
965
+ addOptions() {
966
+ return { read: () => void 0 };
967
+ },
968
+ addProseMirrorPlugins() {
969
+ const options = this.options;
970
+ return [
971
+ new Plugin4({
972
+ key: new PluginKey4("pastedMarkdown"),
973
+ props: {
974
+ // Reached only for a text paste outside code; `plain` is the Shift paste.
975
+ clipboardTextParser: (text, $context, plain, view) => {
976
+ if (plain) return void 0;
977
+ try {
978
+ const blocks = options.read(text.replace(/\r\n?/g, "\n"))?.content ?? [];
979
+ if (blocks.every((block) => block.type === "paragraph")) return void 0;
980
+ if (inTableCell($context) && hasTable(blocks)) return void 0;
981
+ return Slice.maxOpen(view.state.schema.nodeFromJSON({ type: "doc", content: blocks }).content);
982
+ } catch {
983
+ return void 0;
984
+ }
985
+ }
986
+ }
987
+ })
988
+ ];
989
+ }
990
+ });
991
+
992
+ // src/CodeBlockColours.ts
993
+ import { CodeBlockLowlight } from "@tiptap/extension-code-block-lowlight";
994
+
995
+ // src/codeColours.ts
996
+ import { createElement } from "react";
997
+ import { common, createLowlight } from "lowlight";
998
+ var instance;
999
+ function lowlight() {
1000
+ return instance ??= createLowlight(common);
1001
+ }
1002
+
1003
+ // src/CodeBlockColours.ts
1004
+ var namedOnly = {
1005
+ highlight: (language, value) => lowlight().highlight(language, value),
1006
+ highlightAuto: (value) => ({ type: "root", children: [{ type: "text", value }], data: { language: void 0, relevance: 0 } }),
1007
+ listLanguages: () => lowlight().listLanguages(),
1008
+ registered: (language) => lowlight().registered(language)
1009
+ };
1010
+ var CodeBlockColours = CodeBlockLowlight.configure({ lowlight: namedOnly });
1011
+
515
1012
  // src/EditorBlockHandle.tsx
516
1013
  import { useMemo } from "react";
517
1014
  import { IconCopy, IconTrash } from "@tabler/icons-react";
@@ -520,15 +1017,16 @@ import { Selection } from "@tiptap/pm/state";
520
1017
  import { MenuItem, MenuSub } from "@estiva-app/ui";
521
1018
 
522
1019
  // src/ComposerTriggers.tsx
523
- import { Extension as Extension4 } from "@tiptap/core";
524
- import { PluginKey as PluginKey4 } from "@tiptap/pm/state";
1020
+ import { Extension as Extension7 } from "@tiptap/core";
1021
+ import { PluginKey as PluginKey5 } from "@tiptap/pm/state";
525
1022
  import Mention from "@tiptap/extension-mention";
526
1023
  import Suggestion from "@tiptap/suggestion";
527
1024
  import { NodeViewWrapper, ReactNodeViewRenderer } from "@tiptap/react";
528
1025
  import { Avatar } from "@estiva-app/ui";
529
1026
  import { EnterHint } from "@estiva-app/ui";
530
1027
  import { inlineChipClassName } from "@estiva-app/ui";
531
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1028
+ import { WithTooltip } from "@estiva-app/ui";
1029
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
532
1030
  var PEOPLE_LIMIT = 6;
533
1031
  function filterPeople(people, query) {
534
1032
  const q = query.toLowerCase();
@@ -546,8 +1044,8 @@ function peopleMenu(urgent) {
546
1044
  row: (person) => ({
547
1045
  label: person.name,
548
1046
  description: person.description,
549
- leading: person.avatar ?? /* @__PURE__ */ jsx3(Avatar, { size: 32, alt: person.name }),
550
- hint: /* @__PURE__ */ jsx3(EnterHint, {}),
1047
+ leading: person.avatar ?? /* @__PURE__ */ jsx5(Avatar, { size: 32, alt: person.name }),
1048
+ hint: /* @__PURE__ */ jsx5(EnterHint, {}),
551
1049
  size: "tall"
552
1050
  })
553
1051
  };
@@ -674,10 +1172,13 @@ var CaptionedReference = Mention.extend({
674
1172
  addNodeView() {
675
1173
  const { icon, iconFor } = this.options;
676
1174
  return ReactNodeViewRenderer(
677
- ({ node }) => /* @__PURE__ */ jsxs2(NodeViewWrapper, { as: "span", className: inlineChipClassName("neutral", "max-w-[24ch] cursor-default"), children: [
678
- /* @__PURE__ */ jsx3("span", { className: "flex items-center justify-center w-4 h-4 shrink-0 text-text-secondary", children: iconFor?.(node.attrs.uri ?? null) ?? icon }),
679
- /* @__PURE__ */ jsx3("span", { className: "truncate", children: captionOf(node.attrs.label, node.attrs.snippet) })
680
- ] }),
1175
+ ({ node }) => (
1176
+ // The caption is cut at 24ch, so the whole of it is on hover, in the package's tooltip (UIG-14) — as Peek's sent chip has it.
1177
+ /* @__PURE__ */ jsx5(NodeViewWrapper, { as: "span", children: /* @__PURE__ */ jsx5(WithTooltip, { inline: true, label: captionOf(node.attrs.label, node.attrs.snippet), children: /* @__PURE__ */ jsxs4("span", { className: inlineChipClassName("neutral", "max-w-[24ch] cursor-default"), children: [
1178
+ /* @__PURE__ */ jsx5("span", { className: "flex items-center justify-center w-4 h-4 shrink-0 text-text-secondary", children: iconFor?.(node.attrs.uri ?? null) ?? icon }),
1179
+ /* @__PURE__ */ jsx5("span", { className: "truncate", children: captionOf(node.attrs.label, node.attrs.snippet) })
1180
+ ] }) }) })
1181
+ ),
681
1182
  { as: "span" }
682
1183
  );
683
1184
  }
@@ -698,11 +1199,11 @@ function referenceMenu({ ariaLabel, sectionLabel, icon, iconFor }) {
698
1199
  return groups;
699
1200
  },
700
1201
  itemKey: (item) => `reference-${item.id}`,
701
- row: (item) => ({ leading: item.icon ?? iconFor?.(item.uri) ?? icon, label: captionOf(item.label, item.snippet), description: item.description, hint: /* @__PURE__ */ jsx3(EnterHint, {}) })
1202
+ row: (item) => ({ leading: item.icon ?? iconFor?.(item.uri) ?? icon, label: captionOf(item.label, item.snippet), description: item.description, hint: /* @__PURE__ */ jsx5(EnterHint, {}) })
702
1203
  };
703
1204
  }
704
1205
  var listed = (options, query) => options.sections ? options.sections(query).flatMap((section) => section.items.map((item) => ({ ...item, section: section.label }))) : filterReferences(options.items(), query);
705
- var ReferenceTrigger = Extension4.create({
1206
+ var ReferenceTrigger = Extension7.create({
706
1207
  name: "referenceTrigger",
707
1208
  addOptions() {
708
1209
  return { items: () => [], nodeName: "captionedReference", ariaLabel: "References", sectionLabel: "References", icon: null };
@@ -713,7 +1214,7 @@ var ReferenceTrigger = Extension4.create({
713
1214
  Suggestion({
714
1215
  editor: this.editor,
715
1216
  char: "[",
716
- pluginKey: new PluginKey4("referenceTrigger"),
1217
+ pluginKey: new PluginKey5("referenceTrigger"),
717
1218
  ...listening("]"),
718
1219
  // As a mention's: only where the chip may go.
719
1220
  allow: ({ state, range }) => {
@@ -818,7 +1319,7 @@ function filterSlashSections(sections, query) {
818
1319
  const q = query.toLowerCase();
819
1320
  return sections.map((section) => ({ ...section, commands: q ? section.commands.filter((command) => matches(command, q)) : section.commands }));
820
1321
  }
821
- var SlashCommands = Extension4.create({
1322
+ var SlashCommands = Extension7.create({
822
1323
  name: "slashCommands",
823
1324
  addOptions() {
824
1325
  return { sections: [formatSection()] };
@@ -830,7 +1331,7 @@ var SlashCommands = Extension4.create({
830
1331
  editor: this.editor,
831
1332
  char: "/",
832
1333
  startOfLine: false,
833
- pluginKey: new PluginKey4("slashCommands"),
1334
+ pluginKey: new PluginKey5("slashCommands"),
834
1335
  items: ({ query, editor }) => filterSlashSections(sections, query).flatMap(
835
1336
  (section, at) => section.commands.filter((command) => offers(editor, command)).map((command) => ({ ...command, section: at }))
836
1337
  ),
@@ -852,7 +1353,7 @@ var SlashCommands = Extension4.create({
852
1353
  });
853
1354
 
854
1355
  // src/EditorBlockHandle.tsx
855
- import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1356
+ import { Fragment, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
856
1357
  function editorBlocks(view) {
857
1358
  const out = [];
858
1359
  view.state.doc.forEach((node, pos) => {
@@ -919,8 +1420,8 @@ function EditorBlockHandle({ editor, turnInto = turnIntoRows, actions, children,
919
1420
  const node = pos === void 0 ? null : editor.state.doc.nodeAt(pos);
920
1421
  if (pos === void 0 || !node) return null;
921
1422
  const turnRows = typeof turnInto === "function" ? turnInto(node, editor) : node.isTextblock ? turnInto : [];
922
- return /* @__PURE__ */ jsxs3(Fragment, { children: [
923
- turnRows.length > 0 && /* @__PURE__ */ jsx4(MenuSub, { label: "Turn into", children: turnRows.map((row) => /* @__PURE__ */ jsx4(
1423
+ return /* @__PURE__ */ jsxs5(Fragment, { children: [
1424
+ turnRows.length > 0 && /* @__PURE__ */ jsx6(MenuSub, { label: "Turn into", children: turnRows.map((row) => /* @__PURE__ */ jsx6(
924
1425
  MenuItem,
925
1426
  {
926
1427
  label: row.label,
@@ -932,8 +1433,8 @@ function EditorBlockHandle({ editor, turnInto = turnIntoRows, actions, children,
932
1433
  },
933
1434
  row.label
934
1435
  )) }),
935
- /* @__PURE__ */ jsx4(MenuItem, { label: "Duplicate", leading: /* @__PURE__ */ jsx4(IconCopy, { size: 16, stroke: 1.5, className: "text-text-secondary" }), onClick: () => duplicateBlock(editor, pos) }),
936
- /* @__PURE__ */ jsx4(MenuItem, { label: "Delete", destructive: true, leading: /* @__PURE__ */ jsx4(IconTrash, { size: 16, stroke: 1.5, className: "text-error-default" }), onClick: () => deleteBlock(editor, pos) })
1436
+ /* @__PURE__ */ jsx6(MenuItem, { label: "Duplicate", leading: /* @__PURE__ */ jsx6(IconCopy, { size: 16, stroke: 1.5, className: "text-text-secondary" }), onClick: () => duplicateBlock(editor, pos) }),
1437
+ /* @__PURE__ */ jsx6(MenuItem, { label: "Delete", destructive: true, leading: /* @__PURE__ */ jsx6(IconTrash, { size: 16, stroke: 1.5, className: "text-error-default" }), onClick: () => deleteBlock(editor, pos) })
937
1438
  ] });
938
1439
  },
939
1440
  startDrag: (block, event) => {
@@ -955,7 +1456,7 @@ function EditorBlockHandle({ editor, turnInto = turnIntoRows, actions, children,
955
1456
  }
956
1457
  };
957
1458
  }, [editor, turnInto]);
958
- return /* @__PURE__ */ jsx4(BlockHandle, { editing, actions, className, children });
1459
+ return /* @__PURE__ */ jsx6(BlockHandle, { editing, actions, className, children });
959
1460
  }
960
1461
  var LISTS = ["bulletList", "orderedList"];
961
1462
  var isList = (node) => LISTS.includes(node.type.name);
@@ -1061,19 +1562,14 @@ function turnIntoRows(node, editor) {
1061
1562
  }
1062
1563
 
1063
1564
  // src/EditorTableControls.tsx
1064
- import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef3, useState as useState2 } from "react";
1565
+ import { useCallback as useCallback4, useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
1065
1566
  import { TextSelection } from "@tiptap/pm/state";
1066
1567
  import { addColumnAfter, addColumnBefore, addRowAfter, addRowBefore, deleteColumn, deleteRow, deleteTable, toggleHeader } from "@tiptap/pm/tables";
1067
1568
  import { IconColumnInsertLeft, IconColumnInsertRight, IconGripHorizontal, IconGripVertical, IconPlus, IconRowInsertBottom, IconRowInsertTop, IconTrash as IconTrash2 } from "@tabler/icons-react";
1068
- import { cn } from "@estiva-app/ui";
1069
- import { IconButton } from "@estiva-app/ui";
1070
-
1071
- // src/looks.ts
1072
- var FLOATING_SURFACE_CLASSES = "rounded-lg border border-border-default bg-bg-elevated shadow-lg";
1073
-
1074
- // src/EditorTableControls.tsx
1569
+ import { cn as cn3 } from "@estiva-app/ui";
1570
+ import { IconButton as IconButton2 } from "@estiva-app/ui";
1075
1571
  import { Menu, MenuItem as MenuItem2, MenuSeparator } from "@estiva-app/ui";
1076
- import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1572
+ import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1077
1573
  function inCell(editor, cell, command) {
1078
1574
  const { view } = editor;
1079
1575
  if (!view.dom.contains(cell)) return false;
@@ -1119,13 +1615,13 @@ function place(frame, { table, row, cell }) {
1119
1615
  }
1120
1616
  var ICON = { size: 16, stroke: 1.5, className: "text-text-secondary" };
1121
1617
  var keepFocus = (e) => e.preventDefault();
1122
- var HANDLE_BOX = cn(FLOATING_SURFACE_CLASSES, "shadow-none");
1618
+ var HANDLE_BOX = cn3(FLOATING_SURFACE_CLASSES, "shadow-none");
1123
1619
  function EditorTableControls({ editor, children, className }) {
1124
- const frame = useRef3(null);
1125
- const [hovered, setHovered] = useState2(null);
1126
- const [menu, setMenu] = useState2(null);
1620
+ const frame = useRef4(null);
1621
+ const [hovered, setHovered] = useState4(null);
1622
+ const [menu, setMenu] = useState4(null);
1127
1623
  const editable = !!editor && editor.isEditable;
1128
- const follow = useCallback2(
1624
+ const follow = useCallback4(
1129
1625
  (target) => {
1130
1626
  if (!editor || !frame.current || !(target instanceof Element)) return;
1131
1627
  if (target.closest("[data-table-control]")) return;
@@ -1141,7 +1637,7 @@ function EditorTableControls({ editor, children, className }) {
1141
1637
  },
1142
1638
  [editor]
1143
1639
  );
1144
- useEffect2(() => {
1640
+ useEffect4(() => {
1145
1641
  if (!editor) return;
1146
1642
  const changed = ({ transaction }) => {
1147
1643
  if (transaction.docChanged && !menu) setHovered(null);
@@ -1157,11 +1653,11 @@ function EditorTableControls({ editor, children, className }) {
1157
1653
  setMenu(null);
1158
1654
  setHovered(null);
1159
1655
  };
1160
- return /* @__PURE__ */ jsxs4(
1656
+ return /* @__PURE__ */ jsxs6(
1161
1657
  "div",
1162
1658
  {
1163
1659
  ref: frame,
1164
- className: cn("relative", className),
1660
+ className: cn3("relative", className),
1165
1661
  onMouseMove: (e) => {
1166
1662
  if (!menu) follow(e.target);
1167
1663
  },
@@ -1170,39 +1666,39 @@ function EditorTableControls({ editor, children, className }) {
1170
1666
  },
1171
1667
  children: [
1172
1668
  children,
1173
- shown && editor && /* @__PURE__ */ jsxs4(Fragment2, { children: [
1174
- /* @__PURE__ */ jsx5("div", { "data-table-control": true, className: cn(HANDLE_BOX, "absolute -translate-x-1/2 -translate-y-1/2"), style: { left: shown.tableBox.left, top: shown.rowBox.top + shown.rowBox.height / 2 }, children: /* @__PURE__ */ jsxs4(
1669
+ shown && editor && /* @__PURE__ */ jsxs6(Fragment2, { children: [
1670
+ /* @__PURE__ */ jsx7("div", { "data-table-control": true, className: cn3(HANDLE_BOX, "absolute -translate-x-1/2 -translate-y-1/2"), style: { left: shown.tableBox.left, top: shown.rowBox.top + shown.rowBox.height / 2 }, children: /* @__PURE__ */ jsxs6(
1175
1671
  Menu,
1176
1672
  {
1177
1673
  open: menu === "row",
1178
1674
  onOpenChange: (open) => setMenu(open ? "row" : null),
1179
- trigger: /* @__PURE__ */ jsx5(IconButton, { "aria-label": "Row menu", tooltip: "Row", className: "h-6 w-4 p-0", children: /* @__PURE__ */ jsx5(IconGripVertical, { size: 16, stroke: 1.5 }) }),
1675
+ trigger: /* @__PURE__ */ jsx7(IconButton2, { "aria-label": "Row menu", tooltip: "Row", className: "h-6 w-4 p-0", children: /* @__PURE__ */ jsx7(IconGripVertical, { size: 16, stroke: 1.5 }) }),
1180
1676
  children: [
1181
- !isHeaderRow(shown.row) && /* @__PURE__ */ jsx5(MenuItem2, { label: "Insert above", leading: /* @__PURE__ */ jsx5(IconRowInsertTop, { ...ICON }), onClick: run(() => inCell(editor, shown.cell, addRowBefore)) }),
1182
- /* @__PURE__ */ jsx5(MenuItem2, { label: "Insert below", leading: /* @__PURE__ */ jsx5(IconRowInsertBottom, { ...ICON }), onClick: run(() => inCell(editor, shown.cell, addRowAfter)) }),
1183
- /* @__PURE__ */ jsx5(MenuSeparator, {}),
1184
- /* @__PURE__ */ jsx5(MenuItem2, { label: "Delete", destructive: true, leading: /* @__PURE__ */ jsx5(IconTrash2, { size: 16, stroke: 1.5, className: "text-error-default" }), onClick: run(() => deleteTableRow(editor, shown.cell)) }),
1185
- /* @__PURE__ */ jsx5(MenuItem2, { label: "Delete table", destructive: true, leading: /* @__PURE__ */ jsx5(IconTrash2, { size: 16, stroke: 1.5, className: "text-error-default" }), onClick: run(() => inCell(editor, shown.cell, deleteTable)) })
1677
+ !isHeaderRow(shown.row) && /* @__PURE__ */ jsx7(MenuItem2, { label: "Insert above", leading: /* @__PURE__ */ jsx7(IconRowInsertTop, { ...ICON }), onClick: run(() => inCell(editor, shown.cell, addRowBefore)) }),
1678
+ /* @__PURE__ */ jsx7(MenuItem2, { label: "Insert below", leading: /* @__PURE__ */ jsx7(IconRowInsertBottom, { ...ICON }), onClick: run(() => inCell(editor, shown.cell, addRowAfter)) }),
1679
+ /* @__PURE__ */ jsx7(MenuSeparator, {}),
1680
+ /* @__PURE__ */ jsx7(MenuItem2, { label: "Delete", destructive: true, leading: /* @__PURE__ */ jsx7(IconTrash2, { size: 16, stroke: 1.5, className: "text-error-default" }), onClick: run(() => deleteTableRow(editor, shown.cell)) }),
1681
+ /* @__PURE__ */ jsx7(MenuItem2, { label: "Delete table", destructive: true, leading: /* @__PURE__ */ jsx7(IconTrash2, { size: 16, stroke: 1.5, className: "text-error-default" }), onClick: run(() => inCell(editor, shown.cell, deleteTable)) })
1186
1682
  ]
1187
1683
  }
1188
1684
  ) }),
1189
- /* @__PURE__ */ jsx5("div", { "data-table-control": true, className: cn(HANDLE_BOX, "absolute -translate-x-1/2 -translate-y-1/2"), style: { left: shown.columnBox.left + shown.columnBox.width / 2, top: shown.tableBox.top }, children: /* @__PURE__ */ jsxs4(
1685
+ /* @__PURE__ */ jsx7("div", { "data-table-control": true, className: cn3(HANDLE_BOX, "absolute -translate-x-1/2 -translate-y-1/2"), style: { left: shown.columnBox.left + shown.columnBox.width / 2, top: shown.tableBox.top }, children: /* @__PURE__ */ jsxs6(
1190
1686
  Menu,
1191
1687
  {
1192
1688
  open: menu === "column",
1193
1689
  onOpenChange: (open) => setMenu(open ? "column" : null),
1194
- trigger: /* @__PURE__ */ jsx5(IconButton, { "aria-label": "Column menu", tooltip: "Column", className: "h-4 w-6 p-0", children: /* @__PURE__ */ jsx5(IconGripHorizontal, { size: 16, stroke: 1.5 }) }),
1690
+ trigger: /* @__PURE__ */ jsx7(IconButton2, { "aria-label": "Column menu", tooltip: "Column", className: "h-4 w-6 p-0", children: /* @__PURE__ */ jsx7(IconGripHorizontal, { size: 16, stroke: 1.5 }) }),
1195
1691
  children: [
1196
- /* @__PURE__ */ jsx5(MenuItem2, { label: "Insert left", leading: /* @__PURE__ */ jsx5(IconColumnInsertLeft, { ...ICON }), onClick: run(() => inCell(editor, shown.cell, addColumnBefore)) }),
1197
- /* @__PURE__ */ jsx5(MenuItem2, { label: "Insert right", leading: /* @__PURE__ */ jsx5(IconColumnInsertRight, { ...ICON }), onClick: run(() => inCell(editor, shown.cell, addColumnAfter)) }),
1198
- /* @__PURE__ */ jsx5(MenuSeparator, {}),
1199
- /* @__PURE__ */ jsx5(MenuItem2, { label: "Delete", destructive: true, leading: /* @__PURE__ */ jsx5(IconTrash2, { size: 16, stroke: 1.5, className: "text-error-default" }), onClick: run(() => deleteTableColumn(editor, shown.cell)) }),
1200
- /* @__PURE__ */ jsx5(MenuItem2, { label: "Delete table", destructive: true, leading: /* @__PURE__ */ jsx5(IconTrash2, { size: 16, stroke: 1.5, className: "text-error-default" }), onClick: run(() => inCell(editor, shown.cell, deleteTable)) })
1692
+ /* @__PURE__ */ jsx7(MenuItem2, { label: "Insert left", leading: /* @__PURE__ */ jsx7(IconColumnInsertLeft, { ...ICON }), onClick: run(() => inCell(editor, shown.cell, addColumnBefore)) }),
1693
+ /* @__PURE__ */ jsx7(MenuItem2, { label: "Insert right", leading: /* @__PURE__ */ jsx7(IconColumnInsertRight, { ...ICON }), onClick: run(() => inCell(editor, shown.cell, addColumnAfter)) }),
1694
+ /* @__PURE__ */ jsx7(MenuSeparator, {}),
1695
+ /* @__PURE__ */ jsx7(MenuItem2, { label: "Delete", destructive: true, leading: /* @__PURE__ */ jsx7(IconTrash2, { size: 16, stroke: 1.5, className: "text-error-default" }), onClick: run(() => deleteTableColumn(editor, shown.cell)) }),
1696
+ /* @__PURE__ */ jsx7(MenuItem2, { label: "Delete table", destructive: true, leading: /* @__PURE__ */ jsx7(IconTrash2, { size: 16, stroke: 1.5, className: "text-error-default" }), onClick: run(() => inCell(editor, shown.cell, deleteTable)) })
1201
1697
  ]
1202
1698
  }
1203
1699
  ) }),
1204
- shown.row === shown.table.rows[shown.table.rows.length - 1] && /* @__PURE__ */ jsx5("div", { "data-table-control": true, className: cn(HANDLE_BOX, "absolute flex h-4"), style: { left: shown.tableBox.left, top: shown.tableBox.top + shown.tableBox.height, width: shown.tableBox.width }, children: /* @__PURE__ */ jsx5(IconButton, { "aria-label": "Add row", tooltip: "Add row", className: "h-full w-full p-0", onMouseDown: keepFocus, onClick: run(() => appendTableRow(editor, shown.table)), children: /* @__PURE__ */ jsx5(IconPlus, { size: 12, stroke: 1.5 }) }) }),
1205
- shown.cell === shown.row.cells[shown.row.cells.length - 1] && /* @__PURE__ */ jsx5("div", { "data-table-control": true, className: cn(HANDLE_BOX, "absolute flex w-4"), style: { left: shown.tableBox.left + shown.tableBox.width, top: shown.tableBox.top, height: shown.tableBox.height }, children: /* @__PURE__ */ jsx5(IconButton, { "aria-label": "Add column", tooltip: "Add column", className: "h-full w-full p-0", onMouseDown: keepFocus, onClick: run(() => appendTableColumn(editor, shown.table)), children: /* @__PURE__ */ jsx5(IconPlus, { size: 12, stroke: 1.5 }) }) })
1700
+ shown.row === shown.table.rows[shown.table.rows.length - 1] && /* @__PURE__ */ jsx7("div", { "data-table-control": true, className: cn3(HANDLE_BOX, "absolute flex h-4"), style: { left: shown.tableBox.left, top: shown.tableBox.top + shown.tableBox.height, width: shown.tableBox.width }, children: /* @__PURE__ */ jsx7(IconButton2, { "aria-label": "Add row", tooltip: "Add row", className: "h-full w-full p-0", onMouseDown: keepFocus, onClick: run(() => appendTableRow(editor, shown.table)), children: /* @__PURE__ */ jsx7(IconPlus, { size: 12, stroke: 1.5 }) }) }),
1701
+ shown.cell === shown.row.cells[shown.row.cells.length - 1] && /* @__PURE__ */ jsx7("div", { "data-table-control": true, className: cn3(HANDLE_BOX, "absolute flex w-4"), style: { left: shown.tableBox.left + shown.tableBox.width, top: shown.tableBox.top, height: shown.tableBox.height }, children: /* @__PURE__ */ jsx7(IconButton2, { "aria-label": "Add column", tooltip: "Add column", className: "h-full w-full p-0", onMouseDown: keepFocus, onClick: run(() => appendTableColumn(editor, shown.table)), children: /* @__PURE__ */ jsx7(IconPlus, { size: 12, stroke: 1.5 }) }) })
1206
1702
  ] })
1207
1703
  ]
1208
1704
  }
@@ -1210,27 +1706,27 @@ function EditorTableControls({ editor, children, className }) {
1210
1706
  }
1211
1707
 
1212
1708
  // src/BlockNodes.tsx
1213
- import { Extension as Extension8, InputRule, Node as Node2 } from "@tiptap/core";
1709
+ import { Extension as Extension11, InputRule, Node as Node3 } from "@tiptap/core";
1214
1710
  import { DOMSerializer } from "@tiptap/pm/model";
1215
- import { Plugin as Plugin7, PluginKey as PluginKey8 } from "@tiptap/pm/state";
1711
+ import { Plugin as Plugin8, PluginKey as PluginKey9 } from "@tiptap/pm/state";
1216
1712
  import { Mapping, ReplaceAroundStep } from "@tiptap/pm/transform";
1217
1713
  import { NodeViewWrapper as NodeViewWrapper2, ReactNodeViewRenderer as ReactNodeViewRenderer2, ReactRenderer as ReactRenderer2 } from "@tiptap/react";
1218
1714
  import { Checkbox } from "@estiva-app/ui";
1219
1715
  import { calloutLook } from "@estiva-app/ui";
1220
1716
 
1221
1717
  // src/ReferenceMenu.tsx
1222
- import { useCallback as useCallback3 } from "react";
1223
- import { Extension as Extension7 } from "@tiptap/core";
1718
+ import { useCallback as useCallback5 } from "react";
1719
+ import { Extension as Extension10 } from "@tiptap/core";
1224
1720
  import { useEditorState } from "@tiptap/react";
1225
- import { NodeSelection as NodeSelection2, Plugin as Plugin6, PluginKey as PluginKey7, TextSelection as TextSelection2 } from "@tiptap/pm/state";
1721
+ import { NodeSelection as NodeSelection2, Plugin as Plugin7, PluginKey as PluginKey8, TextSelection as TextSelection2 } from "@tiptap/pm/state";
1226
1722
  import { ReplaceStep } from "@tiptap/pm/transform";
1227
1723
  import { IconCheck } from "@tabler/icons-react";
1228
1724
 
1229
1725
  // src/LinkChips.ts
1230
- import { Extension as Extension5 } from "@tiptap/core";
1726
+ import { Extension as Extension8 } from "@tiptap/core";
1231
1727
  import { closeHistory } from "@tiptap/pm/history";
1232
- import { Plugin as Plugin4, PluginKey as PluginKey5 } from "@tiptap/pm/state";
1233
- var key = new PluginKey5("linkChips");
1728
+ import { Plugin as Plugin5, PluginKey as PluginKey6 } from "@tiptap/pm/state";
1729
+ var key = new PluginKey6("linkChips");
1234
1730
  var CHIPS_MADE = "linkChipsMade";
1235
1731
  var PASTES_KEPT = 8;
1236
1732
  var LINK_OR_URL = /(\[[^[\]\n]+\]\((?:https?:\/\/|mailto:)[^\s)]+\))|(https?:\/\/[^\s<>"'`]+)/g;
@@ -1248,10 +1744,10 @@ function urlRuns(text) {
1248
1744
  }
1249
1745
  return out;
1250
1746
  }
1251
- function findLinkChips(doc, selection, referenceOf, pasted = []) {
1747
+ function findLinkChips(doc2, selection, referenceOf, pasted = []) {
1252
1748
  const found = { chips: [], fills: [], open: [] };
1253
1749
  const open = /* @__PURE__ */ new Set();
1254
- doc.descendants((node, pos) => {
1750
+ doc2.descendants((node, pos) => {
1255
1751
  if (node.type.spec.code) return false;
1256
1752
  if (node.type.name === "reference") {
1257
1753
  const href = typeof node.attrs.href === "string" ? node.attrs.href : "";
@@ -1305,7 +1801,7 @@ function refreshLinkChips(editor) {
1305
1801
  const tr = apply(editor.state, findLinkChips(editor.state.doc, editor.state.selection, chips.options.referenceOf, chips.pasted));
1306
1802
  if (tr) editor.view.dispatch(tr);
1307
1803
  }
1308
- var LinkChips = Extension5.create({
1804
+ var LinkChips = Extension8.create({
1309
1805
  name: "linkChips",
1310
1806
  addOptions() {
1311
1807
  return { referenceOf: null, ask: null, limit: 24 };
@@ -1314,7 +1810,7 @@ var LinkChips = Extension5.create({
1314
1810
  const options = this.options;
1315
1811
  const editor = this.editor;
1316
1812
  return [
1317
- new Plugin4({
1813
+ new Plugin5({
1318
1814
  key,
1319
1815
  // The options, where `refreshLinkChips` can read them from the state alone, and where the last pastes landed.
1320
1816
  state: {
@@ -1378,18 +1874,18 @@ import { MenuItem as MenuItem3, MenuSection, MenuSeparator as MenuSeparator2 } f
1378
1874
  import { Popover as Popover2 } from "@estiva-app/ui";
1379
1875
 
1380
1876
  // src/ReferenceCards.ts
1381
- import { Extension as Extension6 } from "@tiptap/core";
1382
- import { NodeSelection, Plugin as Plugin5, PluginKey as PluginKey6 } from "@tiptap/pm/state";
1383
- import { Decoration as Decoration2, DecorationSet as DecorationSet2 } from "@tiptap/pm/view";
1384
- import { useEffect as useEffect3, useState as useState3 } from "react";
1877
+ import { Extension as Extension9 } from "@tiptap/core";
1878
+ import { NodeSelection, Plugin as Plugin6, PluginKey as PluginKey7 } from "@tiptap/pm/state";
1879
+ import { Decoration as Decoration3, DecorationSet as DecorationSet3 } from "@tiptap/pm/view";
1880
+ import { useEffect as useEffect5, useState as useState5 } from "react";
1385
1881
  function layoutOf(raw) {
1386
1882
  if (typeof raw !== "string" || raw === "" || raw === "none") return void 0;
1387
1883
  return raw === "detailed" ? "detailed" : "compact";
1388
1884
  }
1389
- function referenceCardPlacements(doc) {
1885
+ function referenceCardPlacements(doc2) {
1390
1886
  const out = [];
1391
1887
  const seen = /* @__PURE__ */ new Map();
1392
- doc.descendants((node, pos, parent) => {
1888
+ doc2.descendants((node, pos, parent) => {
1393
1889
  if (!node.isTextblock) return true;
1394
1890
  const cards = [];
1395
1891
  node.forEach((child) => {
@@ -1408,7 +1904,7 @@ function referenceCardPlacements(doc) {
1408
1904
  });
1409
1905
  return out;
1410
1906
  }
1411
- var key2 = new PluginKey6("referenceCards");
1907
+ var key2 = new PluginKey7("referenceCards");
1412
1908
  function elementFor(storage, slotKey) {
1413
1909
  let element = storage.elements.get(slotKey);
1414
1910
  if (!element) {
@@ -1422,17 +1918,17 @@ function elementFor(storage, slotKey) {
1422
1918
  function storageOf(editor) {
1423
1919
  return editor.storage.referenceCards;
1424
1920
  }
1425
- var ReferenceCards = Extension6.create({
1921
+ var ReferenceCards = Extension9.create({
1426
1922
  name: "referenceCards",
1427
1923
  addStorage() {
1428
1924
  return { elements: /* @__PURE__ */ new Map() };
1429
1925
  },
1430
1926
  addProseMirrorPlugins() {
1431
1927
  const storage = this.storage;
1432
- const decorate = (doc) => DecorationSet2.create(
1433
- doc,
1434
- referenceCardPlacements(doc).map(
1435
- (p) => Decoration2.widget(p.pos, () => elementFor(storage, p.key), {
1928
+ const decorate = (doc2) => DecorationSet3.create(
1929
+ doc2,
1930
+ referenceCardPlacements(doc2).map(
1931
+ (p) => Decoration3.widget(p.pos, () => elementFor(storage, p.key), {
1436
1932
  key: p.key,
1437
1933
  side: 1,
1438
1934
  ignoreSelection: true,
@@ -1442,7 +1938,7 @@ var ReferenceCards = Extension6.create({
1442
1938
  )
1443
1939
  );
1444
1940
  return [
1445
- new Plugin5({
1941
+ new Plugin6({
1446
1942
  key: key2,
1447
1943
  state: {
1448
1944
  init: (_, state) => decorate(state.doc),
@@ -1454,8 +1950,8 @@ var ReferenceCards = Extension6.create({
1454
1950
  }
1455
1951
  });
1456
1952
  function useReferenceCards(editor) {
1457
- const [slots, setSlots] = useState3([]);
1458
- useEffect3(() => {
1953
+ const [slots, setSlots] = useState5([]);
1954
+ useEffect5(() => {
1459
1955
  if (!editor) return;
1460
1956
  const storage = storageOf(editor);
1461
1957
  if (!storage) return;
@@ -1520,8 +2016,8 @@ function setReferenceCardAt(editor, pos, layout) {
1520
2016
  }
1521
2017
 
1522
2018
  // src/ReferenceMenu.tsx
1523
- import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1524
- var key3 = new PluginKey7("referenceMenu");
2019
+ import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2020
+ var key3 = new PluginKey8("referenceMenu");
1525
2021
  var LAYOUTS = [
1526
2022
  { layout: "none", label: "None" },
1527
2023
  { layout: "compact", label: "Compact" },
@@ -1600,14 +2096,14 @@ function caretAfter(state, pos) {
1600
2096
  const node = state.doc.nodeAt(pos);
1601
2097
  return state.tr.setSelection(TextSelection2.create(state.doc, pos + (node?.nodeSize ?? 1)));
1602
2098
  }
1603
- var ReferenceMenu = Extension7.create({
2099
+ var ReferenceMenu = Extension10.create({
1604
2100
  name: "referenceMenu",
1605
2101
  // Before the editor's own keys, so Enter on a highlighted row is the row's, not a new line.
1606
2102
  priority: 1e3,
1607
2103
  addProseMirrorPlugins() {
1608
2104
  const editor = this.editor;
1609
2105
  return [
1610
- new Plugin6({
2106
+ new Plugin7({
1611
2107
  key: key3,
1612
2108
  state: {
1613
2109
  init: () => null,
@@ -1682,7 +2178,7 @@ function ReferenceMenuView({ editor }) {
1682
2178
  return { pos: open.pos, active: open.active, named: !!node.attrs.uri, layout };
1683
2179
  }
1684
2180
  });
1685
- const keepFocus2 = useCallback3((list) => {
2181
+ const keepFocus2 = useCallback5((list) => {
1686
2182
  const panel = list?.closest('[role="dialog"]');
1687
2183
  if (!panel) return;
1688
2184
  const keep = (event) => event.preventDefault();
@@ -1699,7 +2195,7 @@ function ReferenceMenuView({ editor }) {
1699
2195
  };
1700
2196
  return (
1701
2197
  // finalFocus={false}: focus never left the text.
1702
- /* @__PURE__ */ jsx6(
2198
+ /* @__PURE__ */ jsx8(
1703
2199
  Popover2,
1704
2200
  {
1705
2201
  anchor,
@@ -1711,8 +2207,8 @@ function ReferenceMenuView({ editor }) {
1711
2207
  align: "left",
1712
2208
  ariaLabel: "Link",
1713
2209
  finalFocus: false,
1714
- children: shown && /* @__PURE__ */ jsxs5("div", { ref: keepFocus2, role: "menu", "aria-label": "Link", className: "flex flex-col gap-0.5", children: [
1715
- opens && /* @__PURE__ */ jsx6(
2210
+ children: shown && /* @__PURE__ */ jsxs7("div", { ref: keepFocus2, role: "menu", "aria-label": "Link", className: "flex flex-col gap-0.5", children: [
2211
+ opens && /* @__PURE__ */ jsx8(
1716
2212
  MenuItem3,
1717
2213
  {
1718
2214
  role: "menuitem",
@@ -1725,9 +2221,9 @@ function ReferenceMenuView({ editor }) {
1725
2221
  }
1726
2222
  }
1727
2223
  ),
1728
- shown.named && /* @__PURE__ */ jsxs5(Fragment3, { children: [
1729
- opens && /* @__PURE__ */ jsx6(MenuSeparator2, {}),
1730
- /* @__PURE__ */ jsx6("div", { role: "group", "aria-label": "Card", children: /* @__PURE__ */ jsx6(MenuSection, { label: "Card", children: LAYOUTS.map(({ layout, label }, i) => /* @__PURE__ */ jsx6(
2224
+ shown.named && /* @__PURE__ */ jsxs7(Fragment3, { children: [
2225
+ opens && /* @__PURE__ */ jsx8(MenuSeparator2, {}),
2226
+ /* @__PURE__ */ jsx8("div", { role: "group", "aria-label": "Card", children: /* @__PURE__ */ jsx8(MenuSection, { label: "Card", children: LAYOUTS.map(({ layout, label }, i) => /* @__PURE__ */ jsx8(
1731
2227
  MenuItem3,
1732
2228
  {
1733
2229
  role: "menuitemradio",
@@ -1735,7 +2231,7 @@ function ReferenceMenuView({ editor }) {
1735
2231
  tabIndex: -1,
1736
2232
  label,
1737
2233
  selected: shown.active === i + (opens ? 1 : 0),
1738
- trailing: shown.layout === layout ? /* @__PURE__ */ jsx6(IconCheck, { size: 16, "aria-hidden": true }) : void 0,
2234
+ trailing: shown.layout === layout ? /* @__PURE__ */ jsx8(IconCheck, { size: 16, "aria-hidden": true }) : void 0,
1739
2235
  onMouseDown: (event) => {
1740
2236
  event.preventDefault();
1741
2237
  run(layout);
@@ -1751,7 +2247,7 @@ function ReferenceMenuView({ editor }) {
1751
2247
  }
1752
2248
 
1753
2249
  // src/BlockNodes.tsx
1754
- import { jsx as jsx7 } from "react/jsx-runtime";
2250
+ import { jsx as jsx9 } from "react/jsx-runtime";
1755
2251
  var BLOCK_TYPES = [
1756
2252
  "paragraph",
1757
2253
  "heading",
@@ -1784,9 +2280,9 @@ function holdsText(node) {
1784
2280
  });
1785
2281
  return found;
1786
2282
  }
1787
- function claimsIn(doc) {
2283
+ function claimsIn(doc2) {
1788
2284
  const claims = /* @__PURE__ */ new Map();
1789
- doc.descendants((node, pos, parent) => {
2285
+ doc2.descendants((node, pos, parent) => {
1790
2286
  const fixed = node.type.name === "unknownBlock";
1791
2287
  if (!fixed && !BLOCK_TYPE_SET.has(node.type.name)) return;
1792
2288
  if (parent?.type.name === "listItem" && LISTS2.has(node.type.name)) return;
@@ -1821,8 +2317,8 @@ function textStart({ pos, node }) {
1821
2317
  }
1822
2318
  return start;
1823
2319
  }
1824
- function repeatedBlockIds(doc, before, mapping) {
1825
- const claims = claimsIn(doc);
2320
+ function repeatedBlockIds(doc2, before, mapping) {
2321
+ const claims = claimsIn(doc2);
1826
2322
  let previous;
1827
2323
  const losers = [];
1828
2324
  for (const [id, copies] of claims) {
@@ -1848,7 +2344,7 @@ var idOf = (node) => {
1848
2344
  const id = node.attrs.blockId;
1849
2345
  return typeof id === "string" && id !== "" ? id : void 0;
1850
2346
  };
1851
- function addressedBlocks(doc) {
2347
+ function addressedBlocks(doc2) {
1852
2348
  const out = [];
1853
2349
  const visit = (node, pos) => {
1854
2350
  if (!BLOCK_TYPE_SET.has(node.type.name)) return;
@@ -1858,15 +2354,15 @@ function addressedBlocks(doc) {
1858
2354
  for (const list of innerLists(node, pos)) list.node.forEach((child, offset) => visit(child, list.pos + 1 + offset));
1859
2355
  }
1860
2356
  };
1861
- doc.forEach((node, offset) => visit(node, offset));
2357
+ doc2.forEach((node, offset) => visit(node, offset));
1862
2358
  return out;
1863
2359
  }
1864
- function unnamedBlocks(doc) {
1865
- return addressedBlocks(doc).filter((b) => idOf(b.node) === void 0).map((b) => b.pos);
2360
+ function unnamedBlocks(doc2) {
2361
+ return addressedBlocks(doc2).filter((b) => idOf(b.node) === void 0).map((b) => b.pos);
1866
2362
  }
1867
- function hiddenIds(doc) {
2363
+ function hiddenIds(doc2) {
1868
2364
  const out = [];
1869
- doc.descendants((node, pos) => {
2365
+ doc2.descendants((node, pos) => {
1870
2366
  if (!FLATTENED.has(node.type.name)) return;
1871
2367
  node.forEach((child, offset) => {
1872
2368
  const inner = child.isTextblock || node.type.name === "listItem" && LISTS2.has(child.type.name);
@@ -1875,10 +2371,10 @@ function hiddenIds(doc) {
1875
2371
  });
1876
2372
  return out;
1877
2373
  }
1878
- function handedOver(doc, before, mapping, unnamed) {
2374
+ function handedOver(doc2, before, mapping, unnamed) {
1879
2375
  const out = /* @__PURE__ */ new Map();
1880
2376
  if (unnamed.length === 0) return out;
1881
- const now = new Set(addressedBlocks(doc).map((b) => idOf(b.node)));
2377
+ const now = new Set(addressedBlocks(doc2).map((b) => idOf(b.node)));
1882
2378
  const gone = addressedBlocks(before).filter((b) => !b.node.isLeaf && idOf(b.node) !== void 0 && !now.has(idOf(b.node))).map((b) => ({ ...b, start: textStart(b) })).sort((a, b) => a.start - b.start || b.pos - a.pos);
1883
2379
  for (const old of gone) {
1884
2380
  const id = idOf(old.node);
@@ -1887,7 +2383,7 @@ function handedOver(doc, before, mapping, unnamed) {
1887
2383
  let inner;
1888
2384
  for (const pos of unnamed) {
1889
2385
  if (out.has(pos)) continue;
1890
- if (pos < at && at < pos + doc.nodeAt(pos).nodeSize && (inner === void 0 || pos > inner)) inner = pos;
2386
+ if (pos < at && at < pos + doc2.nodeAt(pos).nodeSize && (inner === void 0 || pos > inner)) inner = pos;
1891
2387
  }
1892
2388
  if (inner !== void 0) {
1893
2389
  out.set(inner, id);
@@ -1909,7 +2405,7 @@ function bringsInBlocks(transactions) {
1909
2405
  })
1910
2406
  );
1911
2407
  }
1912
- var BlockId = Extension8.create({
2408
+ var BlockId = Extension11.create({
1913
2409
  name: "blockId",
1914
2410
  onCreate() {
1915
2411
  const { state, view } = this.editor;
@@ -1921,8 +2417,8 @@ var BlockId = Extension8.create({
1921
2417
  },
1922
2418
  addProseMirrorPlugins() {
1923
2419
  return [
1924
- new Plugin7({
1925
- key: new PluginKey8("blockIdSplit"),
2420
+ new Plugin8({
2421
+ key: new PluginKey9("blockIdSplit"),
1926
2422
  appendTransaction: (transactions, old, state) => {
1927
2423
  if (!bringsInBlocks(transactions)) return null;
1928
2424
  const mapping = new Mapping();
@@ -1957,7 +2453,7 @@ var BlockId = Extension8.create({
1957
2453
  }
1958
2454
  });
1959
2455
  var webUrl = (raw) => raw && /^https?:\/\/\S+$/i.test(raw) ? raw : null;
1960
- var ReferenceNode = Node2.create({
2456
+ var ReferenceNode = Node3.create({
1961
2457
  name: "reference",
1962
2458
  group: "inline",
1963
2459
  inline: true,
@@ -1996,7 +2492,7 @@ var ReferenceNode = Node2.create({
1996
2492
  return ReactNodeViewRenderer2(({ node, editor, getPos }) => {
1997
2493
  const uri = String(node.attrs.uri ?? "");
1998
2494
  const href = typeof node.attrs.href === "string" && node.attrs.href ? node.attrs.href : void 0;
1999
- return /* @__PURE__ */ jsx7(
2495
+ return /* @__PURE__ */ jsx9(
2000
2496
  NodeViewWrapper2,
2001
2497
  {
2002
2498
  as: "span",
@@ -2007,13 +2503,13 @@ var ReferenceNode = Node2.create({
2007
2503
  event.preventDefault();
2008
2504
  event.stopPropagation();
2009
2505
  },
2010
- children: uri ? /* @__PURE__ */ jsx7(View, { uri, ...href ? { href } : {} }) : /* @__PURE__ */ jsx7("span", { children: href })
2506
+ children: uri ? /* @__PURE__ */ jsx9(View, { uri, ...href ? { href } : {} }) : /* @__PURE__ */ jsx9("span", { children: href })
2011
2507
  }
2012
2508
  );
2013
2509
  });
2014
2510
  }
2015
2511
  });
2016
- var UnknownBlock = Node2.create({
2512
+ var UnknownBlock = Node3.create({
2017
2513
  name: "unknownBlock",
2018
2514
  group: "block",
2019
2515
  atom: true,
@@ -2038,7 +2534,7 @@ var UnknownBlock = Node2.create({
2038
2534
  function UnknownBlockView({ node }) {
2039
2535
  const source = node.attrs.source;
2040
2536
  const text = String(node.attrs.text ?? "");
2041
- return /* @__PURE__ */ jsx7(
2537
+ return /* @__PURE__ */ jsx9(
2042
2538
  NodeViewWrapper2,
2043
2539
  {
2044
2540
  "data-block-id": source?.id,
@@ -2048,7 +2544,7 @@ function UnknownBlockView({ node }) {
2048
2544
  }
2049
2545
  );
2050
2546
  }
2051
- var AttachmentNode = Node2.create({
2547
+ var AttachmentNode = Node3.create({
2052
2548
  name: "attachment",
2053
2549
  group: "block",
2054
2550
  atom: true,
@@ -2078,7 +2574,7 @@ var AttachmentNode = Node2.create({
2078
2574
  addNodeView() {
2079
2575
  const View = this.options.view;
2080
2576
  if (!View) return null;
2081
- return ReactNodeViewRenderer2(({ node }) => /* @__PURE__ */ jsx7(NodeViewWrapper2, { className: "my-2", children: /* @__PURE__ */ jsx7(View, { attrs: node.attrs }) }));
2577
+ return ReactNodeViewRenderer2(({ node }) => /* @__PURE__ */ jsx9(NodeViewWrapper2, { className: "my-2", children: /* @__PURE__ */ jsx9(View, { attrs: node.attrs }) }));
2082
2578
  }
2083
2579
  });
2084
2580
  var TICK = "checklistTick";
@@ -2147,7 +2643,7 @@ function listItemView(editor, node, view, getPos) {
2147
2643
  }
2148
2644
  };
2149
2645
  }
2150
- var Checklist = Extension8.create({
2646
+ var Checklist = Extension11.create({
2151
2647
  name: "checklist",
2152
2648
  // Before the list item's own Enter and Backspace.
2153
2649
  priority: 101,
@@ -2212,14 +2708,14 @@ var Checklist = Extension8.create({
2212
2708
  addProseMirrorPlugins() {
2213
2709
  const editor = this.editor;
2214
2710
  return [
2215
- new Plugin7({
2216
- key: new PluginKey8("checklist"),
2711
+ new Plugin8({
2712
+ key: new PluginKey9("checklist"),
2217
2713
  props: { nodeViews: { listItem: (node, view, getPos) => listItemView(editor, node, view, getPos) } }
2218
2714
  })
2219
2715
  ];
2220
2716
  }
2221
2717
  });
2222
- var Callout = Extension8.create({
2718
+ var Callout = Extension11.create({
2223
2719
  name: "callout",
2224
2720
  addGlobalAttributes() {
2225
2721
  return [
@@ -2240,13 +2736,18 @@ var Callout = Extension8.create({
2240
2736
  export {
2241
2737
  AttachmentNode,
2242
2738
  BlockId,
2739
+ COMPOSE_EDITOR_CLASSES,
2243
2740
  Callout,
2244
2741
  CaptionedReference,
2245
2742
  Checklist,
2246
2743
  CodeBlockColours,
2744
+ CommentHighlights,
2745
+ ComposeBox,
2746
+ ComposeKeys,
2247
2747
  EditorBlockHandle,
2248
2748
  EditorTableControls,
2249
2749
  FORMATS,
2750
+ InlineText,
2250
2751
  KeptSelection,
2251
2752
  LinkChips,
2252
2753
  PastedImages,
@@ -2261,10 +2762,13 @@ export {
2261
2762
  SlashCommands,
2262
2763
  UnknownBlock,
2263
2764
  UrgentPersonMention,
2765
+ anchorOfSelection,
2264
2766
  appendTableColumn,
2265
2767
  appendTableRow,
2266
2768
  applyFormat,
2267
2769
  blockDropCursor,
2770
+ commentElement,
2771
+ commentPlace,
2268
2772
  deleteBlock,
2269
2773
  deleteTableColumn,
2270
2774
  deleteTableRow,
@@ -2278,16 +2782,20 @@ export {
2278
2782
  isReferenceMenuOpen,
2279
2783
  isSuggestionActive,
2280
2784
  isSuggestionOpen,
2785
+ keepSelection,
2281
2786
  moveBlock,
2282
2787
  normalizeHref,
2283
2788
  pastedImageName,
2284
2789
  pastedImages,
2285
2790
  peopleMenu,
2791
+ quoteText,
2286
2792
  referenceAtSelection,
2287
2793
  referenceCardPlacements,
2288
2794
  referenceMenu,
2289
2795
  referenceMenuClick,
2290
2796
  refreshLinkChips,
2797
+ releaseSelection,
2798
+ setCommentAnchors,
2291
2799
  setReferenceCard,
2292
2800
  setReferenceCardAt,
2293
2801
  startBlockDrag,