@estiva-app/ui 0.66.0 → 0.68.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 (54) hide show
  1. package/dist/BlockNodes.d.ts +12 -1
  2. package/dist/BlockNodes.d.ts.map +1 -1
  3. package/dist/EditorTableControls.d.ts +38 -0
  4. package/dist/EditorTableControls.d.ts.map +1 -0
  5. package/dist/LinkChips.d.ts +82 -0
  6. package/dist/LinkChips.d.ts.map +1 -0
  7. package/dist/NavItem.d.ts +11 -1
  8. package/dist/NavItem.d.ts.map +1 -1
  9. package/dist/ReferenceCards.d.ts +77 -0
  10. package/dist/ReferenceCards.d.ts.map +1 -0
  11. package/dist/ReferenceMenu.d.ts +18 -0
  12. package/dist/ReferenceMenu.d.ts.map +1 -0
  13. package/dist/RichText.d.ts +9 -2
  14. package/dist/RichText.d.ts.map +1 -1
  15. package/dist/SelectionToolbar.d.ts +9 -1
  16. package/dist/SelectionToolbar.d.ts.map +1 -1
  17. package/dist/editor.d.ts +4 -0
  18. package/dist/editor.d.ts.map +1 -1
  19. package/dist/editor.js +750 -27
  20. package/dist/editor.js.map +4 -4
  21. package/dist/index.js +62 -33
  22. package/dist/index.js.map +4 -4
  23. package/package.json +2 -1
  24. package/registry.json +414 -4
  25. package/src/BlockNodes.test.tsx +2 -1
  26. package/src/BlockNodes.tsx +51 -9
  27. package/src/EditorTableControls.mdx +78 -0
  28. package/src/EditorTableControls.stories.tsx +63 -0
  29. package/src/EditorTableControls.test.tsx +288 -0
  30. package/src/EditorTableControls.tsx +255 -0
  31. package/src/LinkChips.mdx +57 -0
  32. package/src/LinkChips.stories.tsx +67 -0
  33. package/src/LinkChips.test.tsx +178 -0
  34. package/src/LinkChips.ts +243 -0
  35. package/src/NavItem.mdx +10 -0
  36. package/src/NavItem.stories.tsx +23 -1
  37. package/src/NavItem.test.tsx +36 -0
  38. package/src/NavItem.tsx +40 -16
  39. package/src/ReferenceCards.mdx +79 -0
  40. package/src/ReferenceCards.stories.tsx +64 -0
  41. package/src/ReferenceCards.test.tsx +143 -0
  42. package/src/ReferenceCards.ts +225 -0
  43. package/src/ReferenceMenu.mdx +75 -0
  44. package/src/ReferenceMenu.stories.tsx +59 -0
  45. package/src/ReferenceMenu.test.tsx +289 -0
  46. package/src/ReferenceMenu.tsx +346 -0
  47. package/src/RichText.test.tsx +17 -0
  48. package/src/RichText.tsx +36 -4
  49. package/src/SelectionToolbar.tsx +10 -2
  50. package/src/editor.ts +14 -0
  51. package/src/pages.test.ts +3 -0
  52. package/src/registry/registry.test.ts +7 -7
  53. package/src/registry/siblings.test.ts +2 -2
  54. package/stories/Choosing.mdx +4 -0
package/dist/editor.js CHANGED
@@ -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 }) {
86
+ function SelectionToolbar({ editor, marks = ["bold", "italic", "underline"], link = false, children }) {
87
87
  const [anchor, setAnchor] = useState(null);
88
88
  const [mode, setMode] = useState("marks");
89
89
  const [url, setUrl] = useState("");
@@ -273,7 +273,8 @@ function SelectionToolbar({ editor, marks = ["bold", "italic", "underline"], lin
273
273
  onClick: openField,
274
274
  children: /* @__PURE__ */ jsx(IconLink, { size: 16, stroke: 1.5 })
275
275
  }
276
- )
276
+ ),
277
+ children
277
278
  ] })
278
279
  )
279
280
  }
@@ -580,6 +581,155 @@ function EditorBlockHandle({ editor, turnInto = [], actions, children, className
580
581
  return /* @__PURE__ */ jsx3(BlockHandle, { editing, actions, className, children });
581
582
  }
582
583
 
584
+ // src/EditorTableControls.tsx
585
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef3, useState as useState2 } from "react";
586
+ import { TextSelection } from "@tiptap/pm/state";
587
+ import { addColumnAfter, addColumnBefore, addRowAfter, addRowBefore, deleteColumn, deleteRow, deleteTable, toggleHeader } from "@tiptap/pm/tables";
588
+ import { IconColumnInsertLeft, IconColumnInsertRight, IconGripHorizontal, IconGripVertical, IconPlus, IconRowInsertBottom, IconRowInsertTop, IconTrash as IconTrash2 } from "@tabler/icons-react";
589
+ import { cn } from "@estiva-app/ui";
590
+ import { IconButton } from "@estiva-app/ui";
591
+
592
+ // src/looks.ts
593
+ var FLOATING_SURFACE_CLASSES = "rounded-lg border border-border-default bg-bg-elevated shadow-lg";
594
+
595
+ // src/EditorTableControls.tsx
596
+ import { Menu, MenuItem as MenuItem2, MenuSeparator } from "@estiva-app/ui";
597
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
598
+ function inCell(editor, cell, command) {
599
+ const { view } = editor;
600
+ if (!view.dom.contains(cell)) return false;
601
+ const at = view.posAtDOM(cell, 0);
602
+ view.dispatch(view.state.tr.setSelection(TextSelection.near(view.state.doc.resolve(at))));
603
+ const done = command(view.state, view.dispatch, view);
604
+ editor.commands.focus();
605
+ return done;
606
+ }
607
+ function isHeaderRow(row) {
608
+ return row.cells.length > 0 && Array.from(row.cells).every((c) => c.tagName === "TH");
609
+ }
610
+ function deleteTableRow(editor, cell) {
611
+ const row = cell.closest("tr");
612
+ const table = cell.closest("table");
613
+ if (!row || !table || table.rows.length <= 1) return inCell(editor, cell, deleteTable);
614
+ const header = isHeaderRow(row);
615
+ return inCell(editor, cell, header ? (state, dispatch, view) => deleteRow(state, dispatch) && (!view || toggleHeader("row")(view.state, view.dispatch)) : deleteRow);
616
+ }
617
+ function deleteTableColumn(editor, cell) {
618
+ const row = cell.closest("tr");
619
+ return inCell(editor, cell, row && row.cells.length > 1 ? deleteColumn : deleteTable);
620
+ }
621
+ function appendTableRow(editor, table) {
622
+ const last = table.rows[table.rows.length - 1]?.cells[0];
623
+ return !!last && inCell(editor, last, addRowAfter);
624
+ }
625
+ function appendTableColumn(editor, table) {
626
+ const first = table.rows[0];
627
+ const last = first?.cells[first.cells.length - 1];
628
+ return !!last && inCell(editor, last, addColumnAfter);
629
+ }
630
+ function place(frame, { table, row, cell }) {
631
+ const at = frame.getBoundingClientRect();
632
+ const t = table.getBoundingClientRect();
633
+ const r = row.getBoundingClientRect();
634
+ const c = cell.getBoundingClientRect();
635
+ return {
636
+ tableBox: { top: t.top - at.top, left: t.left - at.left, width: t.width, height: t.height },
637
+ rowBox: { top: r.top - at.top, height: r.height },
638
+ columnBox: { left: c.left - at.left, width: c.width }
639
+ };
640
+ }
641
+ var ICON = { size: 16, stroke: 1.5, className: "text-text-secondary" };
642
+ var keepFocus = (e) => e.preventDefault();
643
+ var HANDLE_BOX = cn(FLOATING_SURFACE_CLASSES, "shadow-none");
644
+ function EditorTableControls({ editor, children, className }) {
645
+ const frame = useRef3(null);
646
+ const [hovered, setHovered] = useState2(null);
647
+ const [menu, setMenu] = useState2(null);
648
+ const editable = !!editor && editor.isEditable;
649
+ const follow = useCallback2(
650
+ (target) => {
651
+ if (!editor || !frame.current || !(target instanceof Element)) return;
652
+ if (target.closest("[data-table-control]")) return;
653
+ const cell = target.closest("td, th");
654
+ const row = cell?.closest("tr");
655
+ const table = row?.closest("table");
656
+ if (!cell || !row || !table || !editor.view.dom.contains(table)) {
657
+ setHovered(null);
658
+ return;
659
+ }
660
+ const next = { table, row, cell, ...place(frame.current, { table, row, cell }) };
661
+ setHovered((was) => was && was.cell === cell && JSON.stringify([was.tableBox, was.rowBox, was.columnBox]) === JSON.stringify([next.tableBox, next.rowBox, next.columnBox]) ? was : next);
662
+ },
663
+ [editor]
664
+ );
665
+ useEffect2(() => {
666
+ if (!editor) return;
667
+ const changed = ({ transaction }) => {
668
+ if (transaction.docChanged && !menu) setHovered(null);
669
+ };
670
+ editor.on("transaction", changed);
671
+ return () => {
672
+ editor.off("transaction", changed);
673
+ };
674
+ }, [editor, menu]);
675
+ const shown = editable ? hovered : null;
676
+ const run = (act) => () => {
677
+ if (editor) act();
678
+ setMenu(null);
679
+ setHovered(null);
680
+ };
681
+ return /* @__PURE__ */ jsxs3(
682
+ "div",
683
+ {
684
+ ref: frame,
685
+ className: cn("relative", className),
686
+ onMouseMove: (e) => {
687
+ if (!menu) follow(e.target);
688
+ },
689
+ onMouseLeave: () => {
690
+ if (!menu) setHovered(null);
691
+ },
692
+ children: [
693
+ children,
694
+ shown && editor && /* @__PURE__ */ jsxs3(Fragment2, { children: [
695
+ /* @__PURE__ */ jsx4("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__ */ jsxs3(
696
+ Menu,
697
+ {
698
+ open: menu === "row",
699
+ onOpenChange: (open) => setMenu(open ? "row" : null),
700
+ trigger: /* @__PURE__ */ jsx4(IconButton, { "aria-label": "Row menu", tooltip: "Row", className: "h-6 w-4 p-0", children: /* @__PURE__ */ jsx4(IconGripVertical, { size: 16, stroke: 1.5 }) }),
701
+ children: [
702
+ !isHeaderRow(shown.row) && /* @__PURE__ */ jsx4(MenuItem2, { label: "Insert above", leading: /* @__PURE__ */ jsx4(IconRowInsertTop, { ...ICON }), onClick: run(() => inCell(editor, shown.cell, addRowBefore)) }),
703
+ /* @__PURE__ */ jsx4(MenuItem2, { label: "Insert below", leading: /* @__PURE__ */ jsx4(IconRowInsertBottom, { ...ICON }), onClick: run(() => inCell(editor, shown.cell, addRowAfter)) }),
704
+ /* @__PURE__ */ jsx4(MenuSeparator, {}),
705
+ /* @__PURE__ */ jsx4(MenuItem2, { label: "Delete", destructive: true, leading: /* @__PURE__ */ jsx4(IconTrash2, { size: 16, stroke: 1.5, className: "text-error-default" }), onClick: run(() => deleteTableRow(editor, shown.cell)) }),
706
+ /* @__PURE__ */ jsx4(MenuItem2, { label: "Delete table", destructive: true, leading: /* @__PURE__ */ jsx4(IconTrash2, { size: 16, stroke: 1.5, className: "text-error-default" }), onClick: run(() => inCell(editor, shown.cell, deleteTable)) })
707
+ ]
708
+ }
709
+ ) }),
710
+ /* @__PURE__ */ jsx4("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__ */ jsxs3(
711
+ Menu,
712
+ {
713
+ open: menu === "column",
714
+ onOpenChange: (open) => setMenu(open ? "column" : null),
715
+ trigger: /* @__PURE__ */ jsx4(IconButton, { "aria-label": "Column menu", tooltip: "Column", className: "h-4 w-6 p-0", children: /* @__PURE__ */ jsx4(IconGripHorizontal, { size: 16, stroke: 1.5 }) }),
716
+ children: [
717
+ /* @__PURE__ */ jsx4(MenuItem2, { label: "Insert left", leading: /* @__PURE__ */ jsx4(IconColumnInsertLeft, { ...ICON }), onClick: run(() => inCell(editor, shown.cell, addColumnBefore)) }),
718
+ /* @__PURE__ */ jsx4(MenuItem2, { label: "Insert right", leading: /* @__PURE__ */ jsx4(IconColumnInsertRight, { ...ICON }), onClick: run(() => inCell(editor, shown.cell, addColumnAfter)) }),
719
+ /* @__PURE__ */ jsx4(MenuSeparator, {}),
720
+ /* @__PURE__ */ jsx4(MenuItem2, { label: "Delete", destructive: true, leading: /* @__PURE__ */ jsx4(IconTrash2, { size: 16, stroke: 1.5, className: "text-error-default" }), onClick: run(() => deleteTableColumn(editor, shown.cell)) }),
721
+ /* @__PURE__ */ jsx4(MenuItem2, { label: "Delete table", destructive: true, leading: /* @__PURE__ */ jsx4(IconTrash2, { size: 16, stroke: 1.5, className: "text-error-default" }), onClick: run(() => inCell(editor, shown.cell, deleteTable)) })
722
+ ]
723
+ }
724
+ ) }),
725
+ shown.row === shown.table.rows[shown.table.rows.length - 1] && /* @__PURE__ */ jsx4("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__ */ jsx4(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__ */ jsx4(IconPlus, { size: 12, stroke: 1.5 }) }) }),
726
+ shown.cell === shown.row.cells[shown.row.cells.length - 1] && /* @__PURE__ */ jsx4("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__ */ jsx4(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__ */ jsx4(IconPlus, { size: 12, stroke: 1.5 }) }) })
727
+ ] })
728
+ ]
729
+ }
730
+ );
731
+ }
732
+
583
733
  // src/ComposerTriggers.tsx
584
734
  import { Extension as Extension3 } from "@tiptap/core";
585
735
  import { PluginKey as PluginKey3 } from "@tiptap/pm/state";
@@ -589,7 +739,7 @@ import { NodeViewWrapper, ReactNodeViewRenderer } from "@tiptap/react";
589
739
  import { Avatar } from "@estiva-app/ui";
590
740
  import { EnterHint } from "@estiva-app/ui";
591
741
  import { inlineChipClassName } from "@estiva-app/ui";
592
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
742
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
593
743
  var PEOPLE_LIMIT = 6;
594
744
  function filterPeople(people, query) {
595
745
  const q = query.toLowerCase();
@@ -607,8 +757,8 @@ function peopleMenu(urgent) {
607
757
  row: (person) => ({
608
758
  label: person.name,
609
759
  description: person.description,
610
- leading: person.avatar ?? /* @__PURE__ */ jsx4(Avatar, { size: 32, alt: person.name }),
611
- hint: /* @__PURE__ */ jsx4(EnterHint, {}),
760
+ leading: person.avatar ?? /* @__PURE__ */ jsx5(Avatar, { size: 32, alt: person.name }),
761
+ hint: /* @__PURE__ */ jsx5(EnterHint, {}),
612
762
  size: "tall"
613
763
  })
614
764
  };
@@ -735,9 +885,9 @@ var CaptionedReference = Mention.extend({
735
885
  addNodeView() {
736
886
  const { icon, iconFor } = this.options;
737
887
  return ReactNodeViewRenderer(
738
- ({ node }) => /* @__PURE__ */ jsxs3(NodeViewWrapper, { as: "span", className: inlineChipClassName("neutral", "max-w-[24ch] cursor-default"), children: [
739
- /* @__PURE__ */ jsx4("span", { className: "flex items-center justify-center w-4 h-4 shrink-0 text-text-secondary", children: iconFor?.(node.attrs.uri ?? null) ?? icon }),
740
- /* @__PURE__ */ jsx4("span", { className: "truncate", children: captionOf(node.attrs.label, node.attrs.snippet) })
888
+ ({ node }) => /* @__PURE__ */ jsxs4(NodeViewWrapper, { as: "span", className: inlineChipClassName("neutral", "max-w-[24ch] cursor-default"), children: [
889
+ /* @__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 }),
890
+ /* @__PURE__ */ jsx5("span", { className: "truncate", children: captionOf(node.attrs.label, node.attrs.snippet) })
741
891
  ] }),
742
892
  { as: "span" }
743
893
  );
@@ -759,7 +909,7 @@ function referenceMenu({ ariaLabel, sectionLabel, icon, iconFor }) {
759
909
  return groups;
760
910
  },
761
911
  itemKey: (item) => `reference-${item.id}`,
762
- row: (item) => ({ leading: item.icon ?? iconFor?.(item.uri) ?? icon, label: captionOf(item.label, item.snippet), description: item.description, hint: /* @__PURE__ */ jsx4(EnterHint, {}) })
912
+ row: (item) => ({ leading: item.icon ?? iconFor?.(item.uri) ?? icon, label: captionOf(item.label, item.snippet), description: item.description, hint: /* @__PURE__ */ jsx5(EnterHint, {}) })
763
913
  };
764
914
  }
765
915
  var listed = (options, query) => options.sections ? options.sections(query).flatMap((section) => section.items.map((item) => ({ ...item, section: section.label }))) : filterReferences(options.items(), query);
@@ -913,14 +1063,540 @@ var SlashCommands = Extension3.create({
913
1063
  });
914
1064
 
915
1065
  // src/BlockNodes.tsx
916
- import { Extension as Extension4, InputRule, Node as Node2 } from "@tiptap/core";
1066
+ import { Extension as Extension7, InputRule, Node as Node2 } from "@tiptap/core";
917
1067
  import { DOMSerializer } from "@tiptap/pm/model";
918
- import { Plugin as Plugin3, PluginKey as PluginKey4 } from "@tiptap/pm/state";
1068
+ import { Plugin as Plugin6, PluginKey as PluginKey7 } from "@tiptap/pm/state";
919
1069
  import { Mapping, ReplaceAroundStep } from "@tiptap/pm/transform";
920
1070
  import { NodeViewWrapper as NodeViewWrapper2, ReactNodeViewRenderer as ReactNodeViewRenderer2, ReactRenderer as ReactRenderer2 } from "@tiptap/react";
921
1071
  import { Checkbox } from "@estiva-app/ui";
922
1072
  import { calloutLook } from "@estiva-app/ui";
923
- import { jsx as jsx5 } from "react/jsx-runtime";
1073
+
1074
+ // src/ReferenceMenu.tsx
1075
+ import { useCallback as useCallback3 } from "react";
1076
+ import { Extension as Extension6 } from "@tiptap/core";
1077
+ import { useEditorState } from "@tiptap/react";
1078
+ import { NodeSelection as NodeSelection2, Plugin as Plugin5, PluginKey as PluginKey6, TextSelection as TextSelection2 } from "@tiptap/pm/state";
1079
+ import { ReplaceStep } from "@tiptap/pm/transform";
1080
+ import { IconCheck } from "@tabler/icons-react";
1081
+
1082
+ // src/LinkChips.ts
1083
+ import { Extension as Extension4 } from "@tiptap/core";
1084
+ import { Plugin as Plugin3, PluginKey as PluginKey4 } from "@tiptap/pm/state";
1085
+ var key = new PluginKey4("linkChips");
1086
+ var CHIPS_MADE = "linkChipsMade";
1087
+ var PASTES_KEPT = 8;
1088
+ var LINK_OR_URL = /(\[[^[\]\n]+\]\((?:https?:\/\/|mailto:)[^\s)]+\))|(https?:\/\/[^\s<>"'`]+)/g;
1089
+ var TRAILING = /* @__PURE__ */ new Set([".", ",", ";", ":", "!", "?", ")", "]"]);
1090
+ function urlRuns(text) {
1091
+ if (!text.includes("://")) return [];
1092
+ const out = [];
1093
+ for (const m of text.matchAll(LINK_OR_URL)) {
1094
+ if (m[1]) continue;
1095
+ const start = m.index;
1096
+ if (text.slice(Math.max(0, start - 2), start) === "](" || text[start - 1] === "[") continue;
1097
+ let end = m[0].length;
1098
+ while (end > 0 && TRAILING.has(m[0][end - 1])) end--;
1099
+ out.push({ url: m[0].slice(0, end), from: start, to: start + end, end: start + m[0].length });
1100
+ }
1101
+ return out;
1102
+ }
1103
+ function findLinkChips(doc, selection, referenceOf, pasted = []) {
1104
+ const found = { chips: [], fills: [], open: [] };
1105
+ const open = /* @__PURE__ */ new Set();
1106
+ doc.descendants((node, pos) => {
1107
+ if (node.type.spec.code) return false;
1108
+ if (node.type.name === "reference") {
1109
+ const href = typeof node.attrs.href === "string" ? node.attrs.href : "";
1110
+ if (href && !node.attrs.uri) {
1111
+ const uri = referenceOf?.(href);
1112
+ if (uri) found.fills.push({ pos, uri });
1113
+ else open.add(href);
1114
+ }
1115
+ return false;
1116
+ }
1117
+ if (!node.isText || !node.text) return true;
1118
+ if (node.marks.some((m) => m.type.spec.code || m.type.name === "code" || m.type.name === "link")) return false;
1119
+ for (const run of urlRuns(node.text)) {
1120
+ const from = pos + run.from;
1121
+ const to = pos + run.to;
1122
+ const wasPasted = pasted.some((range) => range.from <= from && pos + run.end <= range.to);
1123
+ const typing = selection && selection.to > from && selection.from <= pos + run.end;
1124
+ if (typing && !(selection.empty && wasPasted)) continue;
1125
+ const uri = referenceOf?.(run.url);
1126
+ if (uri) found.chips.push({ from, to, url: run.url, uri, marks: node.marks.map((m) => m.type.name), pasted: wasPasted });
1127
+ else open.add(run.url);
1128
+ }
1129
+ return false;
1130
+ });
1131
+ found.open = [...open];
1132
+ return found;
1133
+ }
1134
+ function apply(state, found) {
1135
+ if (!found.chips.length && !found.fills.length) return null;
1136
+ const type = state.schema.nodes.reference;
1137
+ if (!type) return null;
1138
+ const tr = state.tr;
1139
+ for (const { pos, uri } of found.fills) {
1140
+ const node = tr.doc.nodeAt(pos);
1141
+ if (node) tr.setNodeMarkup(pos, void 0, { ...node.attrs, uri });
1142
+ }
1143
+ for (const chip of [...found.chips].reverse()) {
1144
+ tr.replaceWith(chip.from, chip.to, type.create({ uri: chip.uri, href: chip.url, marks: chip.marks }));
1145
+ }
1146
+ return tr.setMeta("addToHistory", false).setMeta(CHIPS_MADE, { pasted: found.chips.some((chip) => chip.pasted) });
1147
+ }
1148
+ function refreshLinkChips(editor) {
1149
+ if (editor.isDestroyed) return;
1150
+ const chips = key.getState(editor.state);
1151
+ if (!chips) return;
1152
+ const tr = apply(editor.state, findLinkChips(editor.state.doc, editor.state.selection, chips.options.referenceOf, chips.pasted));
1153
+ if (tr) editor.view.dispatch(tr);
1154
+ }
1155
+ var LinkChips = Extension4.create({
1156
+ name: "linkChips",
1157
+ addOptions() {
1158
+ return { referenceOf: null, ask: null, limit: 24 };
1159
+ },
1160
+ addProseMirrorPlugins() {
1161
+ const options = this.options;
1162
+ const editor = this.editor;
1163
+ return [
1164
+ new Plugin3({
1165
+ key,
1166
+ // The options, where `refreshLinkChips` can read them from the state alone, and where the last pastes landed.
1167
+ state: {
1168
+ init: () => ({ options, pasted: [] }),
1169
+ apply: (tr, value, before, after) => {
1170
+ if (!tr.docChanged) return value;
1171
+ const pasted = value.pasted.map((range) => ({ from: tr.mapping.map(range.from, 1), to: tr.mapping.map(range.to, -1), size: range.to - range.from })).filter((range) => range.from < range.to && range.to - range.from === range.size).map(({ from, to }) => ({ from, to }));
1172
+ if (tr.getMeta("uiEvent") === "paste") {
1173
+ const from = tr.mapping.map(before.selection.from, -1);
1174
+ if (from < after.selection.to) pasted.push({ from, to: after.selection.to });
1175
+ }
1176
+ return { options, pasted: pasted.slice(-PASTES_KEPT) };
1177
+ }
1178
+ },
1179
+ appendTransaction: (trs, _old, state) => {
1180
+ if (!trs.some((tr) => tr.docChanged || tr.selectionSet)) return null;
1181
+ return apply(state, findLinkChips(state.doc, state.selection, options.referenceOf, key.getState(state)?.pasted));
1182
+ },
1183
+ view: (view) => {
1184
+ const asked = /* @__PURE__ */ new Set();
1185
+ const look = (state) => {
1186
+ if (!options.ask) return;
1187
+ const { open } = findLinkChips(state.doc, state.selection, options.referenceOf, key.getState(state)?.pasted);
1188
+ const fresh = open.filter((url) => !asked.has(url)).slice(0, Math.max(0, options.limit - asked.size));
1189
+ if (!fresh.length) return;
1190
+ for (const url of fresh) asked.add(url);
1191
+ const forget = () => {
1192
+ for (const url of fresh) asked.delete(url);
1193
+ };
1194
+ let answer;
1195
+ try {
1196
+ answer = options.ask(fresh);
1197
+ } catch {
1198
+ forget();
1199
+ return;
1200
+ }
1201
+ void Promise.resolve(answer).then(() => refreshLinkChips(editor), forget);
1202
+ };
1203
+ look(view.state);
1204
+ queueMicrotask(() => {
1205
+ try {
1206
+ refreshLinkChips(editor);
1207
+ } catch {
1208
+ }
1209
+ });
1210
+ return {
1211
+ update: (next, prev) => {
1212
+ if (next.state.doc !== prev.doc || !next.state.selection.eq(prev.selection)) look(next.state);
1213
+ }
1214
+ };
1215
+ }
1216
+ })
1217
+ ];
1218
+ }
1219
+ });
1220
+
1221
+ // src/ReferenceMenu.tsx
1222
+ import { MenuItem as MenuItem3, MenuSection, MenuSeparator as MenuSeparator2 } from "@estiva-app/ui";
1223
+ import { Popover as Popover2 } from "@estiva-app/ui";
1224
+
1225
+ // src/ReferenceCards.ts
1226
+ import { Extension as Extension5 } from "@tiptap/core";
1227
+ import { NodeSelection, Plugin as Plugin4, PluginKey as PluginKey5 } from "@tiptap/pm/state";
1228
+ import { Decoration as Decoration2, DecorationSet as DecorationSet2 } from "@tiptap/pm/view";
1229
+ import { useEffect as useEffect3, useState as useState3 } from "react";
1230
+ function layoutOf(raw) {
1231
+ if (typeof raw !== "string" || raw === "" || raw === "none") return void 0;
1232
+ return raw === "detailed" ? "detailed" : "compact";
1233
+ }
1234
+ function referenceCardPlacements(doc) {
1235
+ const out = [];
1236
+ const seen = /* @__PURE__ */ new Map();
1237
+ doc.descendants((node, pos, parent) => {
1238
+ if (!node.isTextblock) return true;
1239
+ const cards = [];
1240
+ node.forEach((child) => {
1241
+ if (child.type.name !== "reference") return;
1242
+ const layout = layoutOf(child.attrs.embed);
1243
+ if (layout && child.attrs.uri) cards.push({ uri: String(child.attrs.uri), layout });
1244
+ });
1245
+ if (cards.length) {
1246
+ const id = node.attrs.blockId ?? parent?.attrs.blockId;
1247
+ const base = id ? `b:${String(id)}` : `p:${pos}`;
1248
+ const n = seen.get(base) ?? 0;
1249
+ seen.set(base, n + 1);
1250
+ out.push({ key: n ? `${base}:${n}` : base, pos: pos + node.nodeSize - 1, cards });
1251
+ }
1252
+ return false;
1253
+ });
1254
+ return out;
1255
+ }
1256
+ var key2 = new PluginKey5("referenceCards");
1257
+ function elementFor(storage, slotKey) {
1258
+ let element = storage.elements.get(slotKey);
1259
+ if (!element) {
1260
+ element = document.createElement("div");
1261
+ element.setAttribute("data-reference-cards", slotKey);
1262
+ element.setAttribute("contenteditable", "false");
1263
+ storage.elements.set(slotKey, element);
1264
+ }
1265
+ return element;
1266
+ }
1267
+ function storageOf(editor) {
1268
+ return editor.storage.referenceCards;
1269
+ }
1270
+ var ReferenceCards = Extension5.create({
1271
+ name: "referenceCards",
1272
+ addStorage() {
1273
+ return { elements: /* @__PURE__ */ new Map() };
1274
+ },
1275
+ addProseMirrorPlugins() {
1276
+ const storage = this.storage;
1277
+ const decorate = (doc) => DecorationSet2.create(
1278
+ doc,
1279
+ referenceCardPlacements(doc).map(
1280
+ (p) => Decoration2.widget(p.pos, () => elementFor(storage, p.key), {
1281
+ key: p.key,
1282
+ side: 1,
1283
+ ignoreSelection: true,
1284
+ // A click on a card is the card's, not a caret placement in the line.
1285
+ stopEvent: () => true
1286
+ })
1287
+ )
1288
+ );
1289
+ return [
1290
+ new Plugin4({
1291
+ key: key2,
1292
+ state: {
1293
+ init: (_, state) => decorate(state.doc),
1294
+ apply: (tr, set) => tr.docChanged ? decorate(tr.doc) : set
1295
+ },
1296
+ props: { decorations: (state) => key2.getState(state) }
1297
+ })
1298
+ ];
1299
+ }
1300
+ });
1301
+ function useReferenceCards(editor) {
1302
+ const [slots, setSlots] = useState3([]);
1303
+ useEffect3(() => {
1304
+ if (!editor) return;
1305
+ const storage = storageOf(editor);
1306
+ if (!storage) return;
1307
+ let last = "";
1308
+ const read = () => {
1309
+ if (editor.isDestroyed) return;
1310
+ const next = referenceCardPlacements(editor.state.doc).map((p) => ({ key: p.key, element: elementFor(storage, p.key), cards: p.cards }));
1311
+ const signature = JSON.stringify(next.map((s) => [s.key, s.cards]));
1312
+ if (signature === last) return;
1313
+ last = signature;
1314
+ const live = new Set(next.map((s) => s.key));
1315
+ for (const k of storage.elements.keys()) if (!live.has(k)) storage.elements.delete(k);
1316
+ setSlots(next);
1317
+ };
1318
+ read();
1319
+ editor.on("transaction", read);
1320
+ return () => {
1321
+ editor.off("transaction", read);
1322
+ };
1323
+ }, [editor]);
1324
+ return slots;
1325
+ }
1326
+ function referenceAtSelection(state) {
1327
+ const { selection } = state;
1328
+ let found = null;
1329
+ if (selection instanceof NodeSelection) {
1330
+ if (selection.node.type.name === "reference") found = { pos: selection.from, node: selection.node };
1331
+ } else if (!selection.empty) {
1332
+ let only = true;
1333
+ state.doc.nodesBetween(selection.from, selection.to, (node2, pos2) => {
1334
+ if (node2.type.name === "reference") {
1335
+ if (found) only = false;
1336
+ found = { pos: pos2, node: node2 };
1337
+ return false;
1338
+ }
1339
+ if (node2.isText) {
1340
+ const from = Math.max(selection.from, pos2) - pos2;
1341
+ const to = Math.min(selection.to, pos2 + node2.nodeSize) - pos2;
1342
+ if ((node2.text ?? "").slice(from, to).trim() !== "") only = false;
1343
+ }
1344
+ return true;
1345
+ });
1346
+ if (!only) found = null;
1347
+ }
1348
+ if (!found) return null;
1349
+ const { pos, node } = found;
1350
+ if (!node.attrs.uri) return null;
1351
+ return { pos, uri: String(node.attrs.uri ?? ""), layout: layoutOf(node.attrs.embed) ?? "none" };
1352
+ }
1353
+ function setReferenceCard(editor, layout) {
1354
+ const at = referenceAtSelection(editor.state);
1355
+ return at ? setReferenceCardAt(editor, at.pos, layout) : false;
1356
+ }
1357
+ function setReferenceCardAt(editor, pos, layout) {
1358
+ const node = editor.state.doc.nodeAt(pos);
1359
+ if (!node || node.type.name !== "reference" || !node.attrs.uri) return false;
1360
+ const wasNode = editor.state.selection instanceof NodeSelection && editor.state.selection.from === pos;
1361
+ const tr = editor.state.tr.setNodeMarkup(pos, void 0, { ...node.attrs, embed: layout === "none" ? null : layout });
1362
+ if (wasNode) tr.setSelection(NodeSelection.create(tr.doc, pos));
1363
+ editor.view.dispatch(tr);
1364
+ return true;
1365
+ }
1366
+
1367
+ // src/ReferenceMenu.tsx
1368
+ import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1369
+ var key3 = new PluginKey6("referenceMenu");
1370
+ var LAYOUTS = [
1371
+ { layout: "none", label: "None" },
1372
+ { layout: "compact", label: "Compact" },
1373
+ { layout: "detailed", label: "Detailed" }
1374
+ ];
1375
+ function chipTarget(view, pos) {
1376
+ const dom = view.nodeDOM(pos);
1377
+ return dom instanceof HTMLElement ? dom.querySelector("a[href], button") : null;
1378
+ }
1379
+ function webHref(view, pos) {
1380
+ const href = view.state.doc.nodeAt(pos)?.attrs.href;
1381
+ return typeof href === "string" && /^https?:\/\//i.test(href) ? href : "";
1382
+ }
1383
+ function rowsOf(view, pos) {
1384
+ const open = chipTarget(view, pos) || webHref(view, pos) ? ["open"] : [];
1385
+ return view.state.doc.nodeAt(pos)?.attrs.uri ? [...open, ...LAYOUTS.map((card) => card.layout)] : open;
1386
+ }
1387
+ function chipAt(state, pos) {
1388
+ const node = state.doc.nodeAt(pos);
1389
+ return node && node.type.name === "reference" ? node : null;
1390
+ }
1391
+ function justPlaced(tr, state) {
1392
+ if (!tr.docChanged || tr.getMeta("history$") || tr.getMeta("preventUpdate")) return null;
1393
+ const whole = tr.steps.some((step) => step instanceof ReplaceStep && step.from === 0 && step.to === tr.before.content.size);
1394
+ if (whole && tr.getMeta("uiEvent") !== "paste") return null;
1395
+ const made = tr.getMeta(CHIPS_MADE);
1396
+ if (made && !made.pasted) return null;
1397
+ const { selection } = state;
1398
+ if (!selection.empty) return null;
1399
+ let at = selection.from;
1400
+ let before = selection.$from.nodeBefore;
1401
+ if (!made && before?.isText && before.text === " ") {
1402
+ at -= 1;
1403
+ before = state.doc.resolve(at).nodeBefore;
1404
+ }
1405
+ if (!before || before.type.name !== "reference" || !before.attrs.uri) return null;
1406
+ const pos = at - before.nodeSize;
1407
+ const old = tr.mapping.invert().map(pos);
1408
+ const was = tr.before.nodeAt(old);
1409
+ if (was && was.type.name === "reference" && was.attrs.uri === before.attrs.uri && tr.mapping.map(old, 1) === pos) return null;
1410
+ return pos;
1411
+ }
1412
+ function dispatchMenu(view, next) {
1413
+ view.dispatch(view.state.tr.setMeta(key3, next));
1414
+ }
1415
+ var passing = false;
1416
+ function openChip(view, pos) {
1417
+ const target = chipTarget(view, pos);
1418
+ if (target) {
1419
+ passing = true;
1420
+ try {
1421
+ target.click();
1422
+ } finally {
1423
+ passing = false;
1424
+ }
1425
+ return;
1426
+ }
1427
+ const href = webHref(view, pos);
1428
+ if (href) window.open(href, "_blank", "noopener,noreferrer");
1429
+ }
1430
+ function choose(editor, pos, row) {
1431
+ dispatchMenu(editor.view, null);
1432
+ if (row === "open") openChip(editor.view, pos);
1433
+ else if (row) setReferenceCardAt(editor, pos, row);
1434
+ }
1435
+ function isReferenceMenuOpen(state) {
1436
+ return !!key3.getState(state);
1437
+ }
1438
+ function referenceMenuClick(editor, pos) {
1439
+ if (passing || editor.isDestroyed || !editor.isEditable || key3.getState(editor.state) === void 0) return false;
1440
+ if (!chipAt(editor.state, pos) || rowsOf(editor.view, pos).length === 0) return false;
1441
+ editor.view.dispatch(caretAfter(editor.state, pos).setMeta(key3, { pos, active: -1 }));
1442
+ return true;
1443
+ }
1444
+ function caretAfter(state, pos) {
1445
+ const node = state.doc.nodeAt(pos);
1446
+ return state.tr.setSelection(TextSelection2.create(state.doc, pos + (node?.nodeSize ?? 1)));
1447
+ }
1448
+ var ReferenceMenu = Extension6.create({
1449
+ name: "referenceMenu",
1450
+ // Before the editor's own keys, so Enter on a highlighted row is the row's, not a new line.
1451
+ priority: 1e3,
1452
+ addProseMirrorPlugins() {
1453
+ const editor = this.editor;
1454
+ return [
1455
+ new Plugin5({
1456
+ key: key3,
1457
+ state: {
1458
+ init: () => null,
1459
+ apply: (tr, value, before, after) => {
1460
+ const meta = tr.getMeta(key3);
1461
+ if (meta !== void 0) return meta;
1462
+ const placed = justPlaced(tr, after);
1463
+ if (placed !== null) return { pos: placed, active: -1 };
1464
+ if (!value) return null;
1465
+ const pos = tr.mapping.map(value.pos);
1466
+ if (!chipAt(after, pos)) return null;
1467
+ if (tr.docChanged && !tr.getMeta("appendedTransaction") && tr.getMeta(CHIPS_MADE) === void 0) return null;
1468
+ const onChip = after.selection instanceof NodeSelection2 && after.selection.from === pos;
1469
+ if (tr.selectionSet && !after.selection.eq(before.selection) && !onChip) return null;
1470
+ return pos === value.pos ? value : { ...value, pos };
1471
+ }
1472
+ },
1473
+ props: {
1474
+ handleDOMEvents: {
1475
+ // Focus gone, the keys no longer reach the menu, and a card chosen now would miss the save on leaving (code review).
1476
+ blur: (view) => {
1477
+ if (key3.getState(view.state)) dispatchMenu(view, null);
1478
+ return false;
1479
+ }
1480
+ },
1481
+ handleKeyDown: (view, event) => {
1482
+ if (event.shiftKey || event.ctrlKey || event.metaKey || event.altKey) return false;
1483
+ const open = key3.getState(view.state);
1484
+ if (!open) {
1485
+ const { selection } = view.state;
1486
+ if (event.key !== "Enter" || !view.editable || !(selection instanceof NodeSelection2) || selection.node.type.name !== "reference") return false;
1487
+ if (rowsOf(view, selection.from).length === 0) return false;
1488
+ view.dispatch(caretAfter(view.state, selection.from).setMeta(key3, { pos: selection.from, active: 0 }));
1489
+ return true;
1490
+ }
1491
+ const rows = rowsOf(view, open.pos);
1492
+ if (event.key === "Escape") {
1493
+ dispatchMenu(view, null);
1494
+ return true;
1495
+ }
1496
+ if (rows.length === 0) return false;
1497
+ if (event.key === "ArrowDown") {
1498
+ dispatchMenu(view, { ...open, active: (open.active + 1) % rows.length });
1499
+ return true;
1500
+ }
1501
+ if (event.key === "ArrowUp") {
1502
+ dispatchMenu(view, { ...open, active: open.active <= 0 ? rows.length - 1 : open.active - 1 });
1503
+ return true;
1504
+ }
1505
+ if (event.key === "Enter" && open.active >= 0) {
1506
+ choose(editor, open.pos, rows[open.active]);
1507
+ return true;
1508
+ }
1509
+ return false;
1510
+ }
1511
+ }
1512
+ })
1513
+ ];
1514
+ }
1515
+ });
1516
+ function ReferenceMenuView({ editor }) {
1517
+ const shown = useEditorState({
1518
+ editor,
1519
+ selector: ({ editor: ed }) => {
1520
+ if (!ed || ed.isDestroyed) return null;
1521
+ const open = key3.getState(ed.state);
1522
+ if (!open) return null;
1523
+ const node = chipAt(ed.state, open.pos);
1524
+ if (!node) return null;
1525
+ const embed = node.attrs.embed;
1526
+ const layout = embed === "detailed" ? "detailed" : typeof embed === "string" && embed !== "" && embed !== "none" ? "compact" : "none";
1527
+ return { pos: open.pos, active: open.active, named: !!node.attrs.uri, layout };
1528
+ }
1529
+ });
1530
+ const keepFocus2 = useCallback3((list) => {
1531
+ const panel = list?.closest('[role="dialog"]');
1532
+ if (!panel) return;
1533
+ const keep = (event) => event.preventDefault();
1534
+ panel.addEventListener("mousedown", keep);
1535
+ return () => panel.removeEventListener("mousedown", keep);
1536
+ }, []);
1537
+ const live = shown && editor && !editor.isDestroyed ? editor : null;
1538
+ const dom = live && shown ? live.view.nodeDOM(shown.pos) : null;
1539
+ const anchor = dom instanceof HTMLElement ? dom : null;
1540
+ const rows = live && shown ? rowsOf(live.view, shown.pos) : [];
1541
+ const opens = rows[0] === "open";
1542
+ const run = (row) => {
1543
+ if (editor && shown) choose(editor, shown.pos, row);
1544
+ };
1545
+ return (
1546
+ // finalFocus={false}: focus never left the text.
1547
+ /* @__PURE__ */ jsx6(
1548
+ Popover2,
1549
+ {
1550
+ anchor,
1551
+ open: !!shown && !!anchor && rows.length > 0,
1552
+ onOpenChange: (next) => {
1553
+ if (!next && editor && !editor.isDestroyed && key3.getState(editor.state)) dispatchMenu(editor.view, null);
1554
+ },
1555
+ side: "bottom",
1556
+ align: "left",
1557
+ ariaLabel: "Link",
1558
+ finalFocus: false,
1559
+ children: shown && /* @__PURE__ */ jsxs5("div", { ref: keepFocus2, role: "menu", "aria-label": "Link", className: "flex flex-col gap-0.5", children: [
1560
+ opens && /* @__PURE__ */ jsx6(
1561
+ MenuItem3,
1562
+ {
1563
+ role: "menuitem",
1564
+ tabIndex: -1,
1565
+ label: "Open",
1566
+ selected: shown.active === 0,
1567
+ onMouseDown: (event) => {
1568
+ event.preventDefault();
1569
+ run("open");
1570
+ }
1571
+ }
1572
+ ),
1573
+ shown.named && /* @__PURE__ */ jsxs5(Fragment3, { children: [
1574
+ opens && /* @__PURE__ */ jsx6(MenuSeparator2, {}),
1575
+ /* @__PURE__ */ jsx6("div", { role: "group", "aria-label": "Card", children: /* @__PURE__ */ jsx6(MenuSection, { label: "Card", children: LAYOUTS.map(({ layout, label }, i) => /* @__PURE__ */ jsx6(
1576
+ MenuItem3,
1577
+ {
1578
+ role: "menuitemradio",
1579
+ "aria-checked": shown.layout === layout,
1580
+ tabIndex: -1,
1581
+ label,
1582
+ selected: shown.active === i + (opens ? 1 : 0),
1583
+ trailing: shown.layout === layout ? /* @__PURE__ */ jsx6(IconCheck, { size: 16, "aria-hidden": true }) : void 0,
1584
+ onMouseDown: (event) => {
1585
+ event.preventDefault();
1586
+ run(layout);
1587
+ }
1588
+ },
1589
+ layout
1590
+ )) }) })
1591
+ ] })
1592
+ ] })
1593
+ }
1594
+ )
1595
+ );
1596
+ }
1597
+
1598
+ // src/BlockNodes.tsx
1599
+ import { jsx as jsx7 } from "react/jsx-runtime";
924
1600
  var BLOCK_TYPES = [
925
1601
  "paragraph",
926
1602
  "heading",
@@ -1078,7 +1754,7 @@ function bringsInBlocks(transactions) {
1078
1754
  })
1079
1755
  );
1080
1756
  }
1081
- var BlockId = Extension4.create({
1757
+ var BlockId = Extension7.create({
1082
1758
  name: "blockId",
1083
1759
  onCreate() {
1084
1760
  const { state, view } = this.editor;
@@ -1090,8 +1766,8 @@ var BlockId = Extension4.create({
1090
1766
  },
1091
1767
  addProseMirrorPlugins() {
1092
1768
  return [
1093
- new Plugin3({
1094
- key: new PluginKey4("blockIdSplit"),
1769
+ new Plugin6({
1770
+ key: new PluginKey7("blockIdSplit"),
1095
1771
  appendTransaction: (transactions, old, state) => {
1096
1772
  if (!bringsInBlocks(transactions)) return null;
1097
1773
  const mapping = new Mapping();
@@ -1125,6 +1801,7 @@ var BlockId = Extension4.create({
1125
1801
  ];
1126
1802
  }
1127
1803
  });
1804
+ var webUrl = (raw) => raw && /^https?:\/\/\S+$/i.test(raw) ? raw : null;
1128
1805
  var ReferenceNode = Node2.create({
1129
1806
  name: "reference",
1130
1807
  group: "inline",
@@ -1137,20 +1814,48 @@ var ReferenceNode = Node2.create({
1137
1814
  },
1138
1815
  addAttributes() {
1139
1816
  return {
1140
- uri: { default: "" },
1141
- marks: { default: [] }
1817
+ // A pasted URL chip keeps only its URL: what it names is the editor's to resolve (`LinkChips`), never
1818
+ // the clipboard's, which could show one object while saving another URL (security review).
1819
+ uri: { default: "", parseHTML: (element) => webUrl(element.getAttribute("data-href")) ? "" : element.getAttribute("data-reference") ?? "" },
1820
+ marks: { default: [] },
1821
+ embed: { default: null, parseHTML: (element) => element.getAttribute("data-embed") },
1822
+ extra: { default: null, parseHTML: () => null },
1823
+ // An http(s) URL only: `LinkChips` makes no other, and a pasted `javascript:` must not reach a saved link.
1824
+ href: { default: null, parseHTML: (element) => webUrl(element.getAttribute("data-href")) }
1142
1825
  };
1143
1826
  },
1144
1827
  parseHTML() {
1145
1828
  return [{ tag: "span[data-reference]" }];
1146
1829
  },
1147
1830
  renderHTML({ node }) {
1148
- return ["span", { "data-reference": String(node.attrs.uri) }, String(node.attrs.uri)];
1831
+ const embed = typeof node.attrs.embed === "string" ? { "data-embed": node.attrs.embed } : {};
1832
+ const href = typeof node.attrs.href === "string" && node.attrs.href ? { "data-href": node.attrs.href } : {};
1833
+ return ["span", { "data-reference": String(node.attrs.uri), ...embed, ...href }, String(node.attrs.href || node.attrs.uri)];
1834
+ },
1835
+ renderText({ node }) {
1836
+ return String(node.attrs.href || node.attrs.uri);
1149
1837
  },
1150
1838
  addNodeView() {
1151
1839
  const View = this.options.view;
1152
1840
  if (!View) return null;
1153
- return ReactNodeViewRenderer2(({ node }) => /* @__PURE__ */ jsx5(NodeViewWrapper2, { as: "span", className: "inline", children: /* @__PURE__ */ jsx5(View, { uri: String(node.attrs.uri ?? "") }) }));
1841
+ return ReactNodeViewRenderer2(({ node, editor, getPos }) => {
1842
+ const uri = String(node.attrs.uri ?? "");
1843
+ const href = typeof node.attrs.href === "string" && node.attrs.href ? node.attrs.href : void 0;
1844
+ return /* @__PURE__ */ jsx7(
1845
+ NodeViewWrapper2,
1846
+ {
1847
+ as: "span",
1848
+ className: "inline",
1849
+ onClickCapture: (event) => {
1850
+ const pos = getPos();
1851
+ if (typeof pos !== "number" || !referenceMenuClick(editor, pos)) return;
1852
+ event.preventDefault();
1853
+ event.stopPropagation();
1854
+ },
1855
+ children: uri ? /* @__PURE__ */ jsx7(View, { uri, ...href ? { href } : {} }) : /* @__PURE__ */ jsx7("span", { children: href })
1856
+ }
1857
+ );
1858
+ });
1154
1859
  }
1155
1860
  });
1156
1861
  var UnknownBlock = Node2.create({
@@ -1178,7 +1883,7 @@ var UnknownBlock = Node2.create({
1178
1883
  function UnknownBlockView({ node }) {
1179
1884
  const source = node.attrs.source;
1180
1885
  const text = String(node.attrs.text ?? "");
1181
- return /* @__PURE__ */ jsx5(
1886
+ return /* @__PURE__ */ jsx7(
1182
1887
  NodeViewWrapper2,
1183
1888
  {
1184
1889
  "data-block-id": source?.id,
@@ -1218,7 +1923,7 @@ var AttachmentNode = Node2.create({
1218
1923
  addNodeView() {
1219
1924
  const View = this.options.view;
1220
1925
  if (!View) return null;
1221
- return ReactNodeViewRenderer2(({ node }) => /* @__PURE__ */ jsx5(NodeViewWrapper2, { className: "my-2", children: /* @__PURE__ */ jsx5(View, { attrs: node.attrs }) }));
1926
+ return ReactNodeViewRenderer2(({ node }) => /* @__PURE__ */ jsx7(NodeViewWrapper2, { className: "my-2", children: /* @__PURE__ */ jsx7(View, { attrs: node.attrs }) }));
1222
1927
  }
1223
1928
  });
1224
1929
  var TICK = "checklistTick";
@@ -1236,7 +1941,7 @@ function itemAtCaret(state) {
1236
1941
  if (item.type.name !== "listItem" || $from.index(-1) !== 0) return null;
1237
1942
  return { node: item, pos: $from.before(-1) };
1238
1943
  }
1239
- var sameAttrs = (a, b) => Object.keys({ ...a.attrs, ...b.attrs }).every((key) => a.attrs[key] === b.attrs[key]);
1944
+ var sameAttrs = (a, b) => Object.keys({ ...a.attrs, ...b.attrs }).every((key4) => a.attrs[key4] === b.attrs[key4]);
1240
1945
  function listItemView(editor, node, view, getPos) {
1241
1946
  const toDOM = node.type.spec.toDOM;
1242
1947
  const { dom, contentDOM } = toDOM ? DOMSerializer.renderSpec(document, toDOM(node)) : { dom: document.createElement("li"), contentDOM: void 0 };
@@ -1287,7 +1992,7 @@ function listItemView(editor, node, view, getPos) {
1287
1992
  }
1288
1993
  };
1289
1994
  }
1290
- var Checklist = Extension4.create({
1995
+ var Checklist = Extension7.create({
1291
1996
  name: "checklist",
1292
1997
  // Before the list item's own Enter and Backspace.
1293
1998
  priority: 101,
@@ -1352,14 +2057,14 @@ var Checklist = Extension4.create({
1352
2057
  addProseMirrorPlugins() {
1353
2058
  const editor = this.editor;
1354
2059
  return [
1355
- new Plugin3({
1356
- key: new PluginKey4("checklist"),
2060
+ new Plugin6({
2061
+ key: new PluginKey7("checklist"),
1357
2062
  props: { nodeViews: { listItem: (node, view, getPos) => listItemView(editor, node, view, getPos) } }
1358
2063
  })
1359
2064
  ];
1360
2065
  }
1361
2066
  });
1362
- var Callout = Extension4.create({
2067
+ var Callout = Extension7.create({
1363
2068
  name: "callout",
1364
2069
  addGlobalAttributes() {
1365
2070
  return [
@@ -1385,25 +2090,36 @@ export {
1385
2090
  Checklist,
1386
2091
  CodeBlockColours,
1387
2092
  EditorBlockHandle,
2093
+ EditorTableControls,
1388
2094
  FORMATS,
1389
2095
  KeptSelection,
2096
+ LinkChips,
1390
2097
  PastedImages,
1391
2098
  PersonMention,
2099
+ ReferenceCards,
2100
+ ReferenceMenu,
2101
+ ReferenceMenuView,
1392
2102
  ReferenceNode,
1393
2103
  ReferenceTrigger,
1394
2104
  SelectionToolbar,
1395
2105
  SlashCommands,
1396
2106
  UnknownBlock,
1397
2107
  UrgentPersonMention,
2108
+ appendTableColumn,
2109
+ appendTableRow,
1398
2110
  applyFormat,
1399
2111
  blockDropCursor,
1400
2112
  deleteBlock,
2113
+ deleteTableColumn,
2114
+ deleteTableRow,
1401
2115
  duplicateBlock,
1402
2116
  filterPeople,
1403
2117
  filterReferences,
1404
2118
  filterSlashSections,
2119
+ findLinkChips,
1405
2120
  formatSection,
1406
2121
  isChecklistTick,
2122
+ isReferenceMenuOpen,
1407
2123
  isSuggestionActive,
1408
2124
  isSuggestionOpen,
1409
2125
  moveBlock,
@@ -1411,10 +2127,17 @@ export {
1411
2127
  pastedImageName,
1412
2128
  pastedImages,
1413
2129
  peopleMenu,
2130
+ referenceAtSelection,
2131
+ referenceCardPlacements,
1414
2132
  referenceMenu,
2133
+ referenceMenuClick,
2134
+ refreshLinkChips,
2135
+ setReferenceCard,
2136
+ setReferenceCardAt,
1415
2137
  startBlockDrag,
1416
2138
  suggestionPopup,
1417
2139
  typeTrigger,
1418
- urgentPubkeys
2140
+ urgentPubkeys,
2141
+ useReferenceCards
1419
2142
  };
1420
2143
  //# sourceMappingURL=editor.js.map