@bettercms-ai/kit 2.0.0-next.1 → 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 (37) hide show
  1. package/dist/adapters/lossy.d.ts +4 -0
  2. package/dist/adapters/lossy.js +8 -2
  3. package/dist/adapters/lossy.js.map +1 -1
  4. package/dist/adapters/schema.js +28 -0
  5. package/dist/adapters/schema.js.map +1 -1
  6. package/dist/controls/rich-text/blocks.d.ts +6 -0
  7. package/dist/controls/rich-text/blocks.js +187 -0
  8. package/dist/controls/rich-text/blocks.js.map +1 -1
  9. package/dist/controls/rich-text/catalog.d.ts +2 -5
  10. package/dist/controls/rich-text/catalog.js +104 -6
  11. package/dist/controls/rich-text/catalog.js.map +1 -1
  12. package/dist/controls/rich-text/codec.d.ts +10 -1
  13. package/dist/controls/rich-text/codec.js +7 -0
  14. package/dist/controls/rich-text/codec.js.map +1 -1
  15. package/dist/controls/rich-text/control.js +1 -0
  16. package/dist/controls/rich-text/control.js.map +1 -1
  17. package/dist/controls/rich-text/dom.d.ts +79 -0
  18. package/dist/controls/rich-text/dom.js +159 -7
  19. package/dist/controls/rich-text/dom.js.map +1 -1
  20. package/dist/controls/rich-text/editor.d.ts +4 -2
  21. package/dist/controls/rich-text/editor.js +93 -3
  22. package/dist/controls/rich-text/editor.js.map +1 -1
  23. package/dist/controls/rich-text/ops.js +6 -0
  24. package/dist/controls/rich-text/ops.js.map +1 -1
  25. package/dist/controls/rich-text/toc.d.ts +3 -2
  26. package/dist/controls/rich-text/toc.js +1 -2
  27. package/dist/controls/rich-text/toc.js.map +1 -1
  28. package/dist/core/core.d.ts +1 -0
  29. package/dist/core/core.js +2 -0
  30. package/dist/core/core.js.map +1 -1
  31. package/dist/core/facets.d.ts +8 -0
  32. package/dist/core/facets.js +8 -0
  33. package/dist/core/facets.js.map +1 -1
  34. package/dist/library/kit-catalog.json +4 -2
  35. package/dist/schema/types.d.ts +8 -0
  36. package/dist/schema/types.js.map +1 -1
  37. package/package.json +2 -2
@@ -9,6 +9,7 @@ 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";
@@ -19,6 +20,7 @@ import { ALL_FEATURES } from "./codec.js";
19
20
  import { htmlToMarkdown, looksLikeMarkdown, markdownToHtml } from "./markdown.js";
20
21
  import { EMPTY_FMT, FormatToolbar, MARK_FEATURE, stylesFor } from "./toolbar.js";
21
22
  import { SelectionBubble } from "./floating.js";
23
+ import { FN_MARK, FOOTNOTE_POPOVER_CLASS, FOOTNOTE_REFUSED, FootnoteEditor, FootnotesList, backToMarker, caretAfterMarker, insertFootnote, renumberFootnotes } from "./footnote.js";
22
24
  import * as React from "react";
23
25
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
24
26
  import { IconArrowBarToDown, IconArrowBarToUp, IconCopy, IconGripVertical, IconPlus, IconTransform, IconTrash } from "@tabler/icons-react";
@@ -175,7 +177,7 @@ function slashPlacement(caret, viewportH) {
175
177
  maxHeight: Math.max(SLASH_MIN_H, Math.min(SLASH_MENU_H, flipUp ? above : below))
176
178
  };
177
179
  }
178
- 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 }) {
179
181
  const hints = shortcutHintsOn();
180
182
  const ph = placeholder ?? (hints ? "Type '/' for commands" : "Write something…");
181
183
  const dense = variant === "panel";
@@ -263,6 +265,11 @@ function RichTextEditor({ value, onChange, readOnly, placeholder, docKey, featur
263
265
  const t = window.setTimeout(() => setNotice(null), 8e3);
264
266
  return () => window.clearTimeout(t);
265
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);
266
273
  const syncIslands = React.useCallback(() => {
267
274
  const el = ref.current;
268
275
  if (!el) return setIslands((p) => p.length ? [] : p);
@@ -286,8 +293,12 @@ function RichTextEditor({ value, onChange, readOnly, placeholder, docKey, featur
286
293
  const el = ref.current;
287
294
  if (!el) return;
288
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"));
289
298
  hydrate(el);
290
299
  syncTaskA11y(el);
300
+ setFootnotes(renumberFootnotes(el));
301
+ outline.current.set(outlineOf(el));
291
302
  known.current = html;
292
303
  setEmpty(isEmptyHtml(html));
293
304
  setWords(countWords(html));
@@ -299,6 +310,8 @@ function RichTextEditor({ value, onChange, readOnly, placeholder, docKey, featur
299
310
  if (!el) return;
300
311
  syncIslands();
301
312
  syncTaskA11y(el);
313
+ outline.current.set(outlineOf(el));
314
+ setFootnotes(renumberFootnotes(el));
302
315
  const html = serialize(el);
303
316
  setEmpty(isEmptyHtml(html) && !el.querySelector(":scope > [data-rt-island], :scope > hr"));
304
317
  setWords(countWords(html));
@@ -584,6 +597,13 @@ function RichTextEditor({ value, onChange, readOnly, placeholder, docKey, featur
584
597
  }
585
598
  if (!it.id || !blockAllowed(it.id, allows)) return;
586
599
  noteRecent(it);
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
+ }
587
607
  if (it.id === "task") insertChecklist(el);
588
608
  else if (it.id === "button") {
589
609
  const a = insertButton(el);
@@ -838,6 +858,28 @@ function RichTextEditor({ value, onChange, readOnly, placeholder, docKey, featur
838
858
  } : { href: "" }
839
859
  });
840
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
+ };
841
883
  /** V2 rich-text-editor.tsx:865-873: a button's popover, its label above the link form */
842
884
  const openButton = (a) => {
843
885
  if (readOnly) return;
@@ -1159,8 +1201,14 @@ function RichTextEditor({ value, onChange, readOnly, placeholder, docKey, featur
1159
1201
  undo: () => api.current.islandUndo(),
1160
1202
  redo: () => api.current.islandRedo(),
1161
1203
  drag: (x, e) => api.current.startDrag(x, e),
1162
- allows: (f) => api.current.allows(f)
1163
- }), [readOnly, dense]);
1204
+ allows: (f) => api.current.allows(f),
1205
+ outline: outline.current,
1206
+ references
1207
+ }), [
1208
+ readOnly,
1209
+ dense,
1210
+ references
1211
+ ]);
1164
1212
  const commentSel = onComment ? () => {
1165
1213
  restoreSel();
1166
1214
  const q = window.getSelection()?.toString().trim();
@@ -1370,6 +1418,11 @@ function RichTextEditor({ value, onChange, readOnly, placeholder, docKey, featur
1370
1418
  emit();
1371
1419
  },
1372
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
+ }
1373
1426
  const a = ref.current ? buttonAt(ref.current, e.target) : null;
1374
1427
  if (!a || readOnly) return;
1375
1428
  e.preventDefault();
@@ -1551,6 +1604,13 @@ function RichTextEditor({ value, onChange, readOnly, placeholder, docKey, featur
1551
1604
  }), el, key))
1552
1605
  ]
1553
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
+ }),
1554
1614
  /* @__PURE__ */ jsxs("div", {
1555
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",
1556
1616
  children: [notice ? /* @__PURE__ */ jsxs("span", {
@@ -1632,6 +1692,35 @@ function RichTextEditor({ value, onChange, readOnly, placeholder, docKey, featur
1632
1692
  maxHeight: slashMaxH
1633
1693
  })
1634
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
+ }),
1635
1724
  /* @__PURE__ */ jsxs(Popover, {
1636
1725
  open: !!link,
1637
1726
  onOpenChange: (o) => !o && setLink(null),
@@ -1691,6 +1780,7 @@ function RichTextEditor({ value, onChange, readOnly, placeholder, docKey, featur
1691
1780
  placeholder,
1692
1781
  docKey: `${docKey}:expanded`,
1693
1782
  features,
1783
+ references,
1694
1784
  single,
1695
1785
  allowNewTab,
1696
1786
  expandCrumbs,