@bettercms-ai/kit 2.0.0-next.0 → 2.0.0-next.2

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 (90) hide show
  1. package/dist/adapters/lossy.d.ts +9 -1
  2. package/dist/adapters/lossy.js +16 -5
  3. package/dist/adapters/lossy.js.map +1 -1
  4. package/dist/adapters/prod-types.d.ts +9 -22
  5. package/dist/adapters/prod-types.js +36 -35
  6. package/dist/adapters/prod-types.js.map +1 -1
  7. package/dist/adapters/schema.js +84 -39
  8. package/dist/adapters/schema.js.map +1 -1
  9. package/dist/context.d.ts +39 -0
  10. package/dist/context.js +40 -1
  11. package/dist/context.js.map +1 -1
  12. package/dist/controls/link.js +112 -196
  13. package/dist/controls/link.js.map +1 -1
  14. package/dist/controls/location.js +3 -8
  15. package/dist/controls/location.js.map +1 -1
  16. package/dist/controls/media.d.ts +0 -14
  17. package/dist/controls/media.js +52 -95
  18. package/dist/controls/media.js.map +1 -1
  19. package/dist/controls/rich-text/article-shell.d.ts +145 -0
  20. package/dist/controls/rich-text/article-shell.js +510 -0
  21. package/dist/controls/rich-text/article-shell.js.map +1 -0
  22. package/dist/controls/rich-text/author.d.ts +25 -0
  23. package/dist/controls/rich-text/author.js +104 -0
  24. package/dist/controls/rich-text/author.js.map +1 -0
  25. package/dist/controls/rich-text/blocks.d.ts +19 -2
  26. package/dist/controls/rich-text/blocks.js +273 -638
  27. package/dist/controls/rich-text/blocks.js.map +1 -1
  28. package/dist/controls/rich-text/button.d.ts +23 -0
  29. package/dist/controls/rich-text/button.js +85 -0
  30. package/dist/controls/rich-text/button.js.map +1 -0
  31. package/dist/controls/rich-text/catalog.d.ts +2 -5
  32. package/dist/controls/rich-text/catalog.js +120 -6
  33. package/dist/controls/rich-text/catalog.js.map +1 -1
  34. package/dist/controls/rich-text/checklist.d.ts +24 -0
  35. package/dist/controls/rich-text/checklist.js +115 -0
  36. package/dist/controls/rich-text/checklist.js.map +1 -0
  37. package/dist/controls/rich-text/codec.d.ts +10 -1
  38. package/dist/controls/rich-text/codec.js +11 -1
  39. package/dist/controls/rich-text/codec.js.map +1 -1
  40. package/dist/controls/rich-text/columns.d.ts +41 -0
  41. package/dist/controls/rich-text/columns.js +181 -0
  42. package/dist/controls/rich-text/columns.js.map +1 -0
  43. package/dist/controls/rich-text/control.js +1 -0
  44. package/dist/controls/rich-text/control.js.map +1 -1
  45. package/dist/controls/rich-text/dom.d.ts +84 -4
  46. package/dist/controls/rich-text/dom.js +171 -10
  47. package/dist/controls/rich-text/dom.js.map +1 -1
  48. package/dist/controls/rich-text/editor.d.ts +4 -2
  49. package/dist/controls/rich-text/editor.js +184 -12
  50. package/dist/controls/rich-text/editor.js.map +1 -1
  51. package/dist/controls/rich-text/embed.d.ts +28 -0
  52. package/dist/controls/rich-text/embed.js +210 -0
  53. package/dist/controls/rich-text/embed.js.map +1 -0
  54. package/dist/controls/rich-text/footnote.d.ts +41 -0
  55. package/dist/controls/rich-text/footnote.js +185 -0
  56. package/dist/controls/rich-text/footnote.js.map +1 -0
  57. package/dist/controls/rich-text/gallery.d.ts +28 -0
  58. package/dist/controls/rich-text/gallery.js +167 -0
  59. package/dist/controls/rich-text/gallery.js.map +1 -0
  60. package/dist/controls/rich-text/link-form.d.ts +3 -1
  61. package/dist/controls/rich-text/link-form.js +2 -2
  62. package/dist/controls/rich-text/link-form.js.map +1 -1
  63. package/dist/controls/rich-text/ops.js +6 -0
  64. package/dist/controls/rich-text/ops.js.map +1 -1
  65. package/dist/controls/rich-text/related.d.ts +36 -0
  66. package/dist/controls/rich-text/related.js +213 -0
  67. package/dist/controls/rich-text/related.js.map +1 -0
  68. package/dist/controls/rich-text/toc.d.ts +47 -0
  69. package/dist/controls/rich-text/toc.js +160 -0
  70. package/dist/controls/rich-text/toc.js.map +1 -0
  71. package/dist/core/core.d.ts +11 -3
  72. package/dist/core/core.js +24 -6
  73. package/dist/core/core.js.map +1 -1
  74. package/dist/core/facets.d.ts +16 -0
  75. package/dist/core/facets.js +17 -1
  76. package/dist/core/facets.js.map +1 -1
  77. package/dist/core/index.js +2 -2
  78. package/dist/index.js +4 -4
  79. package/dist/library/kit-catalog.json +47 -44
  80. package/dist/schema/types.d.ts +15 -0
  81. package/dist/schema/types.js.map +1 -1
  82. package/dist/styles.css +1 -1
  83. package/dist/ui/autocomplete.d.ts +29 -1
  84. package/dist/ui/autocomplete.js +102 -47
  85. package/dist/ui/autocomplete.js.map +1 -1
  86. package/dist/ui/button.js +3 -5
  87. package/dist/ui/button.js.map +1 -1
  88. package/dist/ui/index.js +2 -2
  89. package/dist/ui/toast.js +1 -1
  90. package/package.json +3 -1
@@ -9,14 +9,18 @@ import { COMPONENT_INSERT_TOAST } from "../component-services.js";
9
9
  import { eventKey } from "../../lib/key-event.js";
10
10
  import { closestBlock, createIsland, defaultModel, editingHost, esc, hydrate, isEmptyHtml, placeCaret, rangeRect, rid, sanitize, serialize, textBeforeCaret, topBlock } from "./dom.js";
11
11
  import { LinkForm } from "./link-form.js";
12
+ import { createOutlineStore, outlineOf } from "./toc.js";
12
13
  import { CatalogComponentsProvider, InsertMenuContent, SlashMenuPanel, blockAllowed, catalogKey, noteRecent, useCatalog } from "./catalog.js";
13
14
  import { openComponentPanel } from "./component-panel.js";
14
15
  import { Island } from "./blocks.js";
16
+ import { duplicateBlock, ensureLineAfter, insertBasic, insertIsland, liveInlineCode, liveMarkdown, moveBlock, restrictHtml, turnInto } from "./ops.js";
17
+ import { ButtonLinkPanel, applyButton, buttonAt, buttonInitial, insertButton, removeButton } from "./button.js";
18
+ import { checklistShortcut, insertChecklist, syncTaskA11y, toggleTaskAtPointer, toggleTaskFromKey } from "./checklist.js";
15
19
  import { ALL_FEATURES } from "./codec.js";
16
20
  import { htmlToMarkdown, looksLikeMarkdown, markdownToHtml } from "./markdown.js";
17
- import { duplicateBlock, ensureLineAfter, insertBasic, insertIsland, liveInlineCode, liveMarkdown, moveBlock, restrictHtml, turnInto } from "./ops.js";
18
21
  import { EMPTY_FMT, FormatToolbar, MARK_FEATURE, stylesFor } from "./toolbar.js";
19
22
  import { SelectionBubble } from "./floating.js";
23
+ import { FN_MARK, FOOTNOTE_POPOVER_CLASS, FOOTNOTE_REFUSED, FootnoteEditor, FootnotesList, backToMarker, caretAfterMarker, insertFootnote, renumberFootnotes } from "./footnote.js";
20
24
  import * as React from "react";
21
25
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
22
26
  import { IconArrowBarToDown, IconArrowBarToUp, IconCopy, IconGripVertical, IconPlus, IconTransform, IconTrash } from "@tabler/icons-react";
@@ -112,11 +116,23 @@ function insertHtmlAtCaret(html) {
112
116
  s.addRange(r);
113
117
  }
114
118
  }
115
- var NOT_COUNTED = "[data-rt=component],[data-rt=pullquote],[data-rt=takeaways],[data-rt=embed],[data-rt=video],[data-rt=file]";
119
+ var NOT_COUNTED = [
120
+ "[data-rt=component]",
121
+ "[data-rt=pullquote]",
122
+ "[data-rt=takeaways]",
123
+ "[data-rt=toc]",
124
+ "[data-rt=related]",
125
+ "[data-rt=author]",
126
+ "[data-rt=embed]",
127
+ "[data-rt=video]",
128
+ "[data-rt=file]",
129
+ "[data-rt=footnotes]",
130
+ "sup[data-rt=fn]"
131
+ ].join(",");
116
132
  var WORDS = (s) => s.replace(/\u200b/g, "").trim().split(/\s+/).filter(Boolean).length;
117
133
  function countWords(html) {
118
134
  if (!html) return 0;
119
- if (!/data-rt="(?:component|pullquote|takeaways|embed|video|file)"/.test(html) || typeof DOMParser === "undefined") return WORDS(html.replace(/<\/(p|li|h[1-6]|blockquote|pre|figcaption|td|th|summary|div)>|<br\s*\/?>/g, " ").replace(/<[^>]+>/g, "").replace(/&nbsp;|&[a-z]+;/g, " "));
135
+ if (!/data-rt="(?:component|pullquote|takeaways|toc|related|author|embed|video|file|footnotes|fn)"/.test(html) || typeof DOMParser === "undefined") return WORDS(html.replace(/<[^>]+>/g, " ").replace(/&nbsp;|&[a-z]+;/g, " "));
120
136
  const doc = new DOMParser().parseFromString(`<body>${html}</body>`, "text/html");
121
137
  doc.body.querySelectorAll(NOT_COUNTED).forEach((n) => n.remove());
122
138
  doc.body.querySelectorAll("p,li,h1,h2,h3,h4,h5,h6,td,th,figcaption,summary,pre,blockquote,div").forEach((n) => n.append(" "));
@@ -128,7 +144,7 @@ var countComponents = (html) => (html.match(/data-rt="component"/g) ?? []).lengt
128
144
  var bodyCountLabel = (html) => {
129
145
  const w = countWords(html);
130
146
  const c = countComponents(html);
131
- return `${w.toLocaleString("en-US")} ${w === 1 ? "word" : "words"}${c ? ` · ${c} ${c === 1 ? "block" : "blocks"}` : ""}`;
147
+ return `${w.toLocaleString("en-US")} ${w === 1 ? "word" : "words"}${c ? ` · ${c} ${c === 1 ? "component" : "components"}` : ""}`;
132
148
  };
133
149
  function insertTextAtCaret(text) {
134
150
  if (exec("insertText", text)) return;
@@ -161,7 +177,7 @@ function slashPlacement(caret, viewportH) {
161
177
  maxHeight: Math.max(SLASH_MIN_H, Math.min(SLASH_MENU_H, flipUp ? above : below))
162
178
  };
163
179
  }
164
- function RichTextEditor({ value, onChange, readOnly, placeholder, docKey, features = ALL_FEATURES, single, allowNewTab = true, id, invalid, variant = "document", expandCrumbs, expandedOpen, onExpandedOpenChange, inModal, hideFullscreen, maxHeight, autoFocus, onComment, components }) {
180
+ function RichTextEditor({ value, onChange, readOnly, placeholder, docKey, features = ALL_FEATURES, references, single, allowNewTab = true, id, invalid, variant = "document", expandCrumbs, expandedOpen, onExpandedOpenChange, inModal, hideFullscreen, maxHeight, autoFocus, onComment, components }) {
165
181
  const hints = shortcutHintsOn();
166
182
  const ph = placeholder ?? (hints ? "Type '/' for commands" : "Write something…");
167
183
  const dense = variant === "panel";
@@ -249,6 +265,11 @@ function RichTextEditor({ value, onChange, readOnly, placeholder, docKey, featur
249
265
  const t = window.setTimeout(() => setNotice(null), 8e3);
250
266
  return () => window.clearTimeout(t);
251
267
  }, [notice]);
268
+ /** V2 article-blocks.tsx:45-66: one live outline per editor, so every table of contents follows the headings */
269
+ const outline = React.useRef(createOutlineStore());
270
+ /** V2 rich-text-editor.tsx:271-276: the body's markers in text order, and the one whose note is open */
271
+ const [footnotes, setFootnotes] = React.useState([]);
272
+ const [fnEdit, setFnEdit] = React.useState(null);
252
273
  const syncIslands = React.useCallback(() => {
253
274
  const el = ref.current;
254
275
  if (!el) return setIslands((p) => p.length ? [] : p);
@@ -272,7 +293,12 @@ function RichTextEditor({ value, onChange, readOnly, placeholder, docKey, featur
272
293
  const el = ref.current;
273
294
  if (!el) return;
274
295
  el.innerHTML = sanitize(html || "") || "<p><br></p>";
296
+ el.querySelectorAll(":scope > ol[data-rt=\"footnotes\"]").forEach((n) => n.remove());
297
+ el.querySelectorAll(FN_MARK).forEach((m) => m.setAttribute("contenteditable", "false"));
275
298
  hydrate(el);
299
+ syncTaskA11y(el);
300
+ setFootnotes(renumberFootnotes(el));
301
+ outline.current.set(outlineOf(el));
276
302
  known.current = html;
277
303
  setEmpty(isEmptyHtml(html));
278
304
  setWords(countWords(html));
@@ -283,6 +309,9 @@ function RichTextEditor({ value, onChange, readOnly, placeholder, docKey, featur
283
309
  const el = ref.current;
284
310
  if (!el) return;
285
311
  syncIslands();
312
+ syncTaskA11y(el);
313
+ outline.current.set(outlineOf(el));
314
+ setFootnotes(renumberFootnotes(el));
286
315
  const html = serialize(el);
287
316
  setEmpty(isEmptyHtml(html) && !el.querySelector(":scope > [data-rt-island], :scope > hr"));
288
317
  setWords(countWords(html));
@@ -568,7 +597,21 @@ function RichTextEditor({ value, onChange, readOnly, placeholder, docKey, featur
568
597
  }
569
598
  if (!it.id || !blockAllowed(it.id, allows)) return;
570
599
  noteRecent(it);
571
- insertBasic(el, it.id);
600
+ if (it.id === "footnote") {
601
+ const m = insertFootnote(el);
602
+ if (!m) return void notify(FOOTNOTE_REFUSED.title, { description: FOOTNOTE_REFUSED.description });
603
+ emit();
604
+ openFootnote(m, true);
605
+ return;
606
+ }
607
+ if (it.id === "task") insertChecklist(el);
608
+ else if (it.id === "button") {
609
+ const a = insertButton(el);
610
+ emit();
611
+ refresh();
612
+ openButton(a);
613
+ return;
614
+ } else insertBasic(el, it.id);
572
615
  emit();
573
616
  refresh();
574
617
  };
@@ -645,6 +688,11 @@ function RichTextEditor({ value, onChange, readOnly, placeholder, docKey, featur
645
688
  }
646
689
  const mod = e.metaKey || e.ctrlKey;
647
690
  const k = eventKey(e);
691
+ if (!mod && !e.altKey && !readOnly && ref.current && toggleTaskFromKey(ref.current, e.key)) {
692
+ e.preventDefault();
693
+ emit();
694
+ return;
695
+ }
648
696
  if (mod && !e.altKey && k === "k") {
649
697
  e.preventDefault();
650
698
  saveSel();
@@ -750,6 +798,11 @@ function RichTextEditor({ value, onChange, readOnly, placeholder, docKey, featur
750
798
  hist.current.textAt = Date.now();
751
799
  hist.current.future = [];
752
800
  }
801
+ if (!single && ne.inputType === "insertText" && allows("checklist") && checklistShortcut(ref.current, ne.data)) {
802
+ setSlash(null);
803
+ emit();
804
+ return;
805
+ }
753
806
  if (!single && ne.inputType === "insertText" && (ne.data === " " || ne.data === "-") && liveMarkdown(ref.current, ne.data, allows)) {
754
807
  setSlash(null);
755
808
  emit();
@@ -789,6 +842,7 @@ function RichTextEditor({ value, onChange, readOnly, placeholder, docKey, featur
789
842
  restoreSel();
790
843
  const s = window.getSelection();
791
844
  const anchor = (s?.anchorNode?.nodeType === 1 ? s.anchorNode : s?.anchorNode?.parentElement)?.closest("a") ?? null;
845
+ if (anchor && ref.current && buttonAt(ref.current, anchor)) return openButton(anchor);
792
846
  if (!anchor && (!s || s.isCollapsed || !s.toString().trim())) {
793
847
  notify("Select some text first", { description: hints ? "Then press ⌘K to link it." : "Then link it." });
794
848
  return;
@@ -804,6 +858,50 @@ function RichTextEditor({ value, onChange, readOnly, placeholder, docKey, featur
804
858
  } : { href: "" }
805
859
  });
806
860
  };
861
+ const openFootnote = (m, fresh = false) => {
862
+ if (readOnly) return;
863
+ setBubble(null);
864
+ setFnEdit({
865
+ el: m,
866
+ rect: m.getBoundingClientRect(),
867
+ fresh
868
+ });
869
+ };
870
+ const removeFootnote = (m) => {
871
+ setFnEdit(null);
872
+ m.remove();
873
+ emit();
874
+ };
875
+ /** closed: a new footnote with no note is taken back out (V2), else the caret goes back after its marker */
876
+ const closeFootnote = () => {
877
+ const f = fnEdit;
878
+ setFnEdit(null);
879
+ if (!f || !ref.current) return;
880
+ if (f.fresh && !(f.el.getAttribute("data-note") ?? "").trim()) return removeFootnote(f.el);
881
+ caretAfterMarker(ref.current, f.el);
882
+ };
883
+ /** V2 rich-text-editor.tsx:865-873: a button's popover, its label above the link form */
884
+ const openButton = (a) => {
885
+ if (readOnly) return;
886
+ const { link: initial, label } = buttonInitial(a);
887
+ setBubble(null);
888
+ setLink({
889
+ rect: a.getBoundingClientRect(),
890
+ existing: a,
891
+ initial,
892
+ button: {
893
+ a,
894
+ label
895
+ }
896
+ });
897
+ };
898
+ const saveButton = (v, label) => {
899
+ const b = link?.button;
900
+ setLink(null);
901
+ if (!b) return;
902
+ applyButton(b.a, v, label);
903
+ emit();
904
+ };
807
905
  const saveLink = (v) => {
808
906
  const l = link;
809
907
  setLink(null);
@@ -1072,7 +1170,8 @@ function RichTextEditor({ value, onChange, readOnly, placeholder, docKey, featur
1072
1170
  moveBlock: moveBlock$1,
1073
1171
  islandUndo,
1074
1172
  islandRedo,
1075
- startDrag
1173
+ startDrag,
1174
+ allows
1076
1175
  });
1077
1176
  api.current = {
1078
1177
  emit,
@@ -1085,7 +1184,8 @@ function RichTextEditor({ value, onChange, readOnly, placeholder, docKey, featur
1085
1184
  moveBlock: moveBlock$1,
1086
1185
  islandUndo,
1087
1186
  islandRedo,
1088
- startDrag
1187
+ startDrag,
1188
+ allows
1089
1189
  };
1090
1190
  const islandCtx = React.useMemo(() => ({
1091
1191
  readOnly,
@@ -1100,8 +1200,15 @@ function RichTextEditor({ value, onChange, readOnly, placeholder, docKey, featur
1100
1200
  move: (x, dir) => api.current.moveBlock(x, dir),
1101
1201
  undo: () => api.current.islandUndo(),
1102
1202
  redo: () => api.current.islandRedo(),
1103
- drag: (x, e) => api.current.startDrag(x, e)
1104
- }), [readOnly, dense]);
1203
+ drag: (x, e) => api.current.startDrag(x, e),
1204
+ allows: (f) => api.current.allows(f),
1205
+ outline: outline.current,
1206
+ references
1207
+ }), [
1208
+ readOnly,
1209
+ dense,
1210
+ references
1211
+ ]);
1105
1212
  const commentSel = onComment ? () => {
1106
1213
  restoreSel();
1107
1214
  const q = window.getSelection()?.toString().trim();
@@ -1305,6 +1412,22 @@ function RichTextEditor({ value, onChange, readOnly, placeholder, docKey, featur
1305
1412
  onKeyDown,
1306
1413
  onKeyUp: refresh,
1307
1414
  onMouseUp: refresh,
1415
+ onMouseDown: (e) => {
1416
+ if (readOnly || !ref.current || !toggleTaskAtPointer(ref.current, e.target, e.clientX)) return;
1417
+ e.preventDefault();
1418
+ emit();
1419
+ },
1420
+ onClick: (e) => {
1421
+ const fn = e.target.closest?.(FN_MARK);
1422
+ if (fn && ref.current?.contains(fn) && !fn.closest("[data-rt-island]") && !readOnly) {
1423
+ e.preventDefault();
1424
+ return openFootnote(fn);
1425
+ }
1426
+ const a = ref.current ? buttonAt(ref.current, e.target) : null;
1427
+ if (!a || readOnly) return;
1428
+ e.preventDefault();
1429
+ openButton(a);
1430
+ },
1308
1431
  onPaste: (e) => {
1309
1432
  if (readOnly) return;
1310
1433
  if (Array.from(e.clipboardData.files).filter((f) => f.type.startsWith("image/")).length) {
@@ -1481,6 +1604,13 @@ function RichTextEditor({ value, onChange, readOnly, placeholder, docKey, featur
1481
1604
  }), el, key))
1482
1605
  ]
1483
1606
  }),
1607
+ /* @__PURE__ */ jsx(FootnotesList, {
1608
+ notes: footnotes,
1609
+ readOnly,
1610
+ density: dense ? "dense" : "default",
1611
+ onEdit: (m) => openFootnote(m),
1612
+ onBack: (m) => ref.current && backToMarker(ref.current, m)
1613
+ }),
1484
1614
  /* @__PURE__ */ jsxs("div", {
1485
1615
  className: "flex h-7 items-center justify-end gap-3 rounded-b-kit-lg border-t border-kit-line-subtle px-3 text-kit-2xs text-kit-fg-3",
1486
1616
  children: [notice ? /* @__PURE__ */ jsxs("span", {
@@ -1506,7 +1636,7 @@ function RichTextEditor({ value, onChange, readOnly, placeholder, docKey, featur
1506
1636
  words.toLocaleString("en-US"),
1507
1637
  " ",
1508
1638
  words === 1 ? "word" : "words",
1509
- comps ? ` · ${comps} ${comps === 1 ? "block" : "blocks"}` : "",
1639
+ comps ? ` · ${comps} ${comps === 1 ? "component" : "components"}` : "",
1510
1640
  " · ",
1511
1641
  Math.max(1, Math.round(words / 220)),
1512
1642
  " min read"
@@ -1562,6 +1692,35 @@ function RichTextEditor({ value, onChange, readOnly, placeholder, docKey, featur
1562
1692
  maxHeight: slashMaxH
1563
1693
  })
1564
1694
  })] }), document.body) : null,
1695
+ /* @__PURE__ */ jsxs(Popover, {
1696
+ open: !!fnEdit,
1697
+ onOpenChange: (o) => !o && closeFootnote(),
1698
+ children: [/* @__PURE__ */ jsx(PopoverAnchor, { render: /* @__PURE__ */ jsx("span", {
1699
+ className: "pointer-events-none fixed",
1700
+ style: fnEdit ? {
1701
+ top: fnEdit.rect.bottom,
1702
+ left: fnEdit.rect.left,
1703
+ width: Math.max(1, fnEdit.rect.width),
1704
+ height: 1
1705
+ } : {
1706
+ top: 0,
1707
+ left: 0
1708
+ }
1709
+ }) }), /* @__PURE__ */ jsx(PopoverContent, {
1710
+ className: FOOTNOTE_POPOVER_CLASS,
1711
+ finalFocus: false,
1712
+ children: fnEdit ? /* @__PURE__ */ jsx(FootnoteEditor, {
1713
+ n: footnotes.findIndex((f) => f.el === fnEdit.el) + 1,
1714
+ note: fnEdit.el.getAttribute("data-note") ?? "",
1715
+ onChange: (v) => {
1716
+ fnEdit.el.setAttribute("data-note", v);
1717
+ emit();
1718
+ },
1719
+ onRemove: () => removeFootnote(fnEdit.el),
1720
+ onDone: closeFootnote
1721
+ }, footnotes.findIndex((f) => f.el === fnEdit.el)) : null
1722
+ })]
1723
+ }),
1565
1724
  /* @__PURE__ */ jsxs(Popover, {
1566
1725
  open: !!link,
1567
1726
  onOpenChange: (o) => !o && setLink(null),
@@ -1579,7 +1738,19 @@ function RichTextEditor({ value, onChange, readOnly, placeholder, docKey, featur
1579
1738
  }) }), /* @__PURE__ */ jsx(PopoverContent, {
1580
1739
  className: "w-[340px] rounded-kit-lg p-3",
1581
1740
  finalFocus: false,
1582
- children: link ? /* @__PURE__ */ jsx("div", {
1741
+ children: link?.button ? /* @__PURE__ */ jsx(ButtonLinkPanel, {
1742
+ initial: link.initial,
1743
+ label: link.button.label,
1744
+ allowNewTab,
1745
+ onSave: saveButton,
1746
+ onCancel: () => setLink(null),
1747
+ onDetach: () => {
1748
+ const a = link.button.a;
1749
+ setLink(null);
1750
+ if (ref.current) removeButton(ref.current, a);
1751
+ emit();
1752
+ }
1753
+ }) : link ? /* @__PURE__ */ jsx("div", {
1583
1754
  className: "flex flex-col gap-2",
1584
1755
  children: /* @__PURE__ */ jsx(LinkForm, {
1585
1756
  initial: link.initial,
@@ -1609,6 +1780,7 @@ function RichTextEditor({ value, onChange, readOnly, placeholder, docKey, featur
1609
1780
  placeholder,
1610
1781
  docKey: `${docKey}:expanded`,
1611
1782
  features,
1783
+ references,
1612
1784
  single,
1613
1785
  allowNewTab,
1614
1786
  expandCrumbs,