@trafica/editor 1.0.32 → 1.0.34

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.
package/dist/index.js CHANGED
@@ -1635,38 +1635,47 @@ var htmlSerializer = {
1635
1635
  return parseHTMLBody(parsed.body);
1636
1636
  }
1637
1637
  };
1638
+ function alignAttrs(align, extraStyles = "") {
1639
+ const dataAlign = align ? ` data-align="${align}"` : "";
1640
+ const textAlign = align && align !== "left" ? `text-align:${align};` : "";
1641
+ const combined = textAlign + extraStyles;
1642
+ const style = combined ? ` style="${combined}"` : "";
1643
+ return dataAlign + style;
1644
+ }
1638
1645
  function serializeBlock(node, idCounts = /* @__PURE__ */ new Map(), inCell = false) {
1639
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A;
1646
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B;
1640
1647
  switch (node.type) {
1641
1648
  case "paragraph": {
1642
- const align = ((_a = node.attrs) == null ? void 0 : _a.align) ? ` data-align="${node.attrs.align}"` : "";
1643
- const style = inCell ? ` style="margin:0;min-height:1.2em;"` : "";
1644
- return `<p${align}${style}>${serializeChildren(node.children)}</p>`;
1649
+ const align = (_a = node.attrs) == null ? void 0 : _a.align;
1650
+ const extra = inCell ? "margin:0;min-height:1.2em;" : "";
1651
+ return `<p${alignAttrs(align, extra)}>${serializeChildren(node.children)}</p>`;
1645
1652
  }
1646
1653
  case "heading": {
1647
1654
  const l = (_c = (_b = node.attrs) == null ? void 0 : _b.level) != null ? _c : 1;
1648
- const align = ((_d = node.attrs) == null ? void 0 : _d.align) ? ` data-align="${node.attrs.align}"` : "";
1655
+ const align = (_d = node.attrs) == null ? void 0 : _d.align;
1649
1656
  const rawText = node.children.filter((c) => "text" in c).map((c) => c.text).join("");
1650
1657
  const base = slugify(rawText) || `heading-${l}`;
1651
1658
  const count = (_e = idCounts.get(base)) != null ? _e : 0;
1652
1659
  idCounts.set(base, count + 1);
1653
1660
  const id = count === 0 ? base : `${base}-${count}`;
1654
- return `<h${l} id="${id}"${align}>${serializeChildren(node.children)}</h${l}>`;
1661
+ return `<h${l} id="${id}"${alignAttrs(align)}>${serializeChildren(node.children)}</h${l}>`;
1655
1662
  }
1656
1663
  case "blockquote": {
1657
- const align = ((_f = node.attrs) == null ? void 0 : _f.align) ? ` data-align="${node.attrs.align}"` : "";
1658
- return `<blockquote${align}>${serializeChildren(node.children)}</blockquote>`;
1664
+ const align = (_f = node.attrs) == null ? void 0 : _f.align;
1665
+ return `<blockquote${alignAttrs(align)}>${serializeChildren(node.children)}</blockquote>`;
1659
1666
  }
1660
1667
  case "bullet_list":
1661
1668
  return `<ul>${serializeChildren(node.children)}</ul>`;
1662
1669
  case "ordered_list":
1663
1670
  return `<ol>${serializeChildren(node.children)}</ol>`;
1664
- case "list_item":
1665
- return `<li>${serializeChildren(node.children)}</li>`;
1671
+ case "list_item": {
1672
+ const align = (_g = node.attrs) == null ? void 0 : _g.align;
1673
+ return `<li${alignAttrs(align)}>${serializeChildren(node.children)}</li>`;
1674
+ }
1666
1675
  case "check_list":
1667
1676
  return `<ul class="todo-list" data-type="checklist">${serializeChildren(node.children)}</ul>`;
1668
1677
  case "check_list_item": {
1669
- const checked = !!((_g = node.attrs) == null ? void 0 : _g.checked);
1678
+ const checked = !!((_h = node.attrs) == null ? void 0 : _h.checked);
1670
1679
  const dataChecked = checked ? ' data-checked="true"' : "";
1671
1680
  const itemClass = `todo-list__item${checked ? " todo-list__item_checked" : ""}`;
1672
1681
  const checkedAttr = checked ? ' checked="checked"' : "";
@@ -1674,16 +1683,16 @@ function serializeBlock(node, idCounts = /* @__PURE__ */ new Map(), inCell = fal
1674
1683
  return `<li class="${itemClass}"${dataChecked}><label class="todo-list__label"><input type="checkbox" disabled="disabled"${checkedAttr}><span class="todo-list__label__description">${innerContent}</span></label></li>`;
1675
1684
  }
1676
1685
  case "code_block": {
1677
- const lang = ((_h = node.attrs) == null ? void 0 : _h.language) ? ` class="language-${node.attrs.language}"` : "";
1686
+ const lang = ((_i = node.attrs) == null ? void 0 : _i.language) ? ` class="language-${node.attrs.language}"` : "";
1678
1687
  const raw = node.children.map((c) => isTextNode(c) ? escapeHTML(c.text) : "").join("");
1679
1688
  return `<pre><code${lang}>${raw}</code></pre>`;
1680
1689
  }
1681
1690
  case "image": {
1682
- const src = escapeAttr((_j = (_i = node.attrs) == null ? void 0 : _i.src) != null ? _j : "");
1683
- const alt = escapeAttr((_l = (_k = node.attrs) == null ? void 0 : _k.alt) != null ? _l : "");
1684
- const width = ((_m = node.attrs) == null ? void 0 : _m.width) ? ` width="${node.attrs.width}"` : "";
1685
- const align = ((_n = node.attrs) == null ? void 0 : _n.align) ? ` data-align="${node.attrs.align}"` : "";
1686
- const caption = ((_o = node.attrs) == null ? void 0 : _o.caption) ? escapeHTML(node.attrs.caption) : "";
1691
+ const src = escapeAttr((_k = (_j = node.attrs) == null ? void 0 : _j.src) != null ? _k : "");
1692
+ const alt = escapeAttr((_m = (_l = node.attrs) == null ? void 0 : _l.alt) != null ? _m : "");
1693
+ const width = ((_n = node.attrs) == null ? void 0 : _n.width) ? ` width="${node.attrs.width}"` : "";
1694
+ const align = ((_o = node.attrs) == null ? void 0 : _o.align) ? ` data-align="${node.attrs.align}"` : "";
1695
+ const caption = ((_p = node.attrs) == null ? void 0 : _p.caption) ? escapeHTML(node.attrs.caption) : "";
1687
1696
  if (caption) {
1688
1697
  return `<figure${align}><img src="${src}" alt="${alt}"${width} /><figcaption>${caption}</figcaption></figure>`;
1689
1698
  }
@@ -1693,7 +1702,7 @@ function serializeBlock(node, idCounts = /* @__PURE__ */ new Map(), inCell = fal
1693
1702
  return "<hr />";
1694
1703
  case "table": {
1695
1704
  const rows = node.children.map((c) => serializeBlock(c)).join("");
1696
- const colWidths = (_q = (_p = node.attrs) == null ? void 0 : _p.colWidths) != null ? _q : [];
1705
+ const colWidths = (_r = (_q = node.attrs) == null ? void 0 : _q.colWidths) != null ? _r : [];
1697
1706
  let colgroup = "";
1698
1707
  if (colWidths.length > 0) {
1699
1708
  const total = colWidths.reduce((s, w) => s + w, 0) || colWidths.length * 120;
@@ -1706,15 +1715,15 @@ function serializeBlock(node, idCounts = /* @__PURE__ */ new Map(), inCell = fal
1706
1715
  return `<tr>${cells}</tr>`;
1707
1716
  }
1708
1717
  case "table_cell": {
1709
- if ((_r = node.attrs) == null ? void 0 : _r.covered) return "";
1710
- const cs = ((_s = node.attrs) == null ? void 0 : _s.colspan) > 1 ? ` colspan="${(_t = node.attrs) == null ? void 0 : _t.colspan}"` : "";
1711
- const rs = ((_u = node.attrs) == null ? void 0 : _u.rowspan) > 1 ? ` rowspan="${(_v = node.attrs) == null ? void 0 : _v.rowspan}"` : "";
1718
+ if ((_s = node.attrs) == null ? void 0 : _s.covered) return "";
1719
+ const cs = ((_t = node.attrs) == null ? void 0 : _t.colspan) > 1 ? ` colspan="${(_u = node.attrs) == null ? void 0 : _u.colspan}"` : "";
1720
+ const rs = ((_v = node.attrs) == null ? void 0 : _v.rowspan) > 1 ? ` rowspan="${(_w = node.attrs) == null ? void 0 : _w.rowspan}"` : "";
1712
1721
  return `<td${cs}${rs} style="border:1px solid #d1d5db;padding:0.5em 0.75em;vertical-align:top;min-width:40px;word-break:break-word;">${node.children.map((c) => serializeBlock(c, /* @__PURE__ */ new Map(), true)).join("")}</td>`;
1713
1722
  }
1714
1723
  case "table_header": {
1715
- if ((_w = node.attrs) == null ? void 0 : _w.covered) return "";
1716
- const cs = ((_x = node.attrs) == null ? void 0 : _x.colspan) > 1 ? ` colspan="${(_y = node.attrs) == null ? void 0 : _y.colspan}"` : "";
1717
- const rs = ((_z = node.attrs) == null ? void 0 : _z.rowspan) > 1 ? ` rowspan="${(_A = node.attrs) == null ? void 0 : _A.rowspan}"` : "";
1724
+ if ((_x = node.attrs) == null ? void 0 : _x.covered) return "";
1725
+ const cs = ((_y = node.attrs) == null ? void 0 : _y.colspan) > 1 ? ` colspan="${(_z = node.attrs) == null ? void 0 : _z.colspan}"` : "";
1726
+ const rs = ((_A = node.attrs) == null ? void 0 : _A.rowspan) > 1 ? ` rowspan="${(_B = node.attrs) == null ? void 0 : _B.rowspan}"` : "";
1718
1727
  return `<th${cs}${rs} style="border:1px solid #d1d5db;padding:0.5em 0.75em;vertical-align:top;min-width:40px;word-break:break-word;background:#f9fafb;font-weight:600;text-align:left;">${node.children.map((c) => serializeBlock(c, /* @__PURE__ */ new Map(), true)).join("")}</th>`;
1719
1728
  }
1720
1729
  default:
@@ -7833,7 +7842,7 @@ var jsonSerializer = {
7833
7842
  }
7834
7843
  }
7835
7844
  };
7836
- function EditorCore({
7845
+ function Editor({
7837
7846
  engine,
7838
7847
  placeholder = "Start writing...",
7839
7848
  className = "",
@@ -7841,14 +7850,11 @@ function EditorCore({
7841
7850
  onHTMLChange,
7842
7851
  onJSONChange,
7843
7852
  onOpenLinkPopup,
7844
- onUploadImage: _onUploadImage,
7845
- onFocus: onFocusProp,
7846
- onBlur: onBlurProp,
7847
- hideToolbar = false,
7848
- toolbarConfig
7853
+ onUploadImage: _onUploadImage
7849
7854
  }) {
7850
7855
  const containerRef = react.useRef(null);
7851
7856
  const isRenderingRef = react.useRef(false);
7857
+ const skipRestoreSelectionRef = react.useRef(false);
7852
7858
  const scrollCaretIntoView = react.useCallback(() => {
7853
7859
  var _a, _b;
7854
7860
  const sel = window.getSelection();
@@ -7961,8 +7967,14 @@ function EditorCore({
7961
7967
  react.useLayoutEffect(() => {
7962
7968
  const container = containerRef.current;
7963
7969
  if (!container || !state.selection) return;
7970
+ if (skipRestoreSelectionRef.current) {
7971
+ skipRestoreSelectionRef.current = false;
7972
+ return;
7973
+ }
7974
+ isRenderingRef.current = true;
7964
7975
  restoreSelection(container, state.selection);
7965
7976
  scrollCaretIntoView();
7977
+ isRenderingRef.current = false;
7966
7978
  }, [state.selection]);
7967
7979
  react.useEffect(() => {
7968
7980
  const container = containerRef.current;
@@ -8027,6 +8039,7 @@ function EditorCore({
8027
8039
  if (currentSelection && JSON.stringify(currentSelection.anchor) === JSON.stringify(captured.anchor) && JSON.stringify(currentSelection.focus) === JSON.stringify(captured.focus)) {
8028
8040
  return;
8029
8041
  }
8042
+ skipRestoreSelectionRef.current = true;
8030
8043
  const tr = createTransaction();
8031
8044
  tr.steps.push(tr_setSelection(captured));
8032
8045
  engine.dispatch(tr);
@@ -8053,11 +8066,7 @@ function EditorCore({
8053
8066
  );
8054
8067
  engine.dispatch(tr);
8055
8068
  }
8056
- onFocusProp == null ? void 0 : onFocusProp();
8057
- }, [engine, onFocusProp]);
8058
- const handleBlur = react.useCallback(() => {
8059
- onBlurProp == null ? void 0 : onBlurProp();
8060
- }, [onBlurProp]);
8069
+ }, [engine]);
8061
8070
  const handleKeyDown = react.useCallback(
8062
8071
  (e) => {
8063
8072
  var _a, _b, _c;
@@ -8409,6 +8418,7 @@ function EditorCore({
8409
8418
  if (!container) return;
8410
8419
  const selection = captureSelection(container);
8411
8420
  if (!selection) return;
8421
+ skipRestoreSelectionRef.current = true;
8412
8422
  const tr = createTransaction();
8413
8423
  tr.steps.push(
8414
8424
  tr_setSelection(selection)
@@ -8491,7 +8501,7 @@ function EditorCore({
8491
8501
  className: `editor-root relative flex flex-col ${className}`,
8492
8502
  style: editorHeight ? { minHeight: editorHeight } : void 0,
8493
8503
  children: [
8494
- !readOnly && !hideToolbar && /* @__PURE__ */ jsxRuntime.jsx(
8504
+ !readOnly && /* @__PURE__ */ jsxRuntime.jsx(
8495
8505
  Toolbar,
8496
8506
  {
8497
8507
  engine,
@@ -8502,8 +8512,7 @@ function EditorCore({
8502
8512
  linkPopupOpen,
8503
8513
  onLinkPopupClose: () => setLinkPopupOpen(false),
8504
8514
  isSourceMode,
8505
- onToggleSource: handleToggleSource,
8506
- toolbarConfig
8515
+ onToggleSource: handleToggleSource
8507
8516
  }
8508
8517
  ),
8509
8518
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative", children: isSourceMode ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -8530,7 +8539,7 @@ function EditorCore({
8530
8539
  isVisualEmpty && /* @__PURE__ */ jsxRuntime.jsx(
8531
8540
  "div",
8532
8541
  {
8533
- className: "\r\n absolute\r\n top-6\r\n left-6\r\n pointer-events-none\r\n select-none\r\n text-base\r\n text-gray-400\r\n \r\n ",
8542
+ className: "\r\n absolute\r\n top-6\r\n left-6\r\n pointer-events-none\r\n select-none\r\n text-base\r\n text-gray-400\r\n dark:text-gray-500\r\n ",
8534
8543
  "aria-hidden": "true",
8535
8544
  children: placeholder
8536
8545
  }
@@ -8548,7 +8557,6 @@ function EditorCore({
8548
8557
  onMouseDown: handleMouseDown,
8549
8558
  onContextMenu: handleContextMenu,
8550
8559
  onFocus: handleFocus,
8551
- onBlur: handleBlur,
8552
8560
  onClick: handleClick,
8553
8561
  onKeyDown: handleKeyDown,
8554
8562
  className: [
@@ -8560,7 +8568,7 @@ function EditorCore({
8560
8568
  "text-base",
8561
8569
  "leading-relaxed",
8562
8570
  "text-gray-900",
8563
- "",
8571
+ "dark:text-gray-100",
8564
8572
  readOnly ? "cursor-default" : "cursor-text"
8565
8573
  ].join(" ")
8566
8574
  }
@@ -8570,7 +8578,7 @@ function EditorCore({
8570
8578
  {
8571
8579
  role: "tooltip",
8572
8580
  style: { left: linkTooltip.x, top: linkTooltip.y },
8573
- className: "absolute z-50 pointer-events-none flex items-center gap-1.5 bg-gray-900 text-white text-xs px-2.5 py-1.5 rounded shadow-lg max-w-xs",
8581
+ className: "absolute z-50 pointer-events-none flex items-center gap-1.5 bg-gray-900 dark:bg-gray-700 text-white text-xs px-2.5 py-1.5 rounded shadow-lg max-w-xs",
8574
8582
  children: [
8575
8583
  /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "11", height: "11", viewBox: "0 0 16 16", fill: "none", className: "shrink-0 opacity-70", "aria-hidden": "true", children: [
8576
8584
  /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6.5 3.5H4A2.5 2.5 0 0 0 4 8.5h2", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" }),
@@ -8685,168 +8693,1020 @@ function prettyPrintHTML(html) {
8685
8693
  }
8686
8694
  return result.join("\n");
8687
8695
  }
8688
-
8689
- // src/editor/table/TablePlugin.ts
8690
- function getCellDOMElement(cellPathJson) {
8691
- return document.querySelector(`[data-block-path='${cellPathJson}']`);
8692
- }
8693
- function isCaretAtContainerStart(container) {
8694
- const sel = window.getSelection();
8695
- if (!sel || sel.rangeCount === 0 || !sel.isCollapsed) return false;
8696
- const range = sel.getRangeAt(0);
8697
- if (range.startOffset !== 0) return false;
8698
- let node = range.startContainer;
8699
- while (node && node !== container) {
8700
- if (node.previousSibling) return false;
8701
- node = node.parentNode;
8702
- }
8703
- return node === container;
8704
- }
8705
- function isCaretAtContainerEnd(container) {
8706
- var _a, _b;
8707
- const sel = window.getSelection();
8708
- if (!sel || sel.rangeCount === 0 || !sel.isCollapsed) return false;
8709
- const range = sel.getRangeAt(0);
8710
- const endNode = range.endContainer;
8711
- const endOffset = range.endOffset;
8712
- const len = endNode.nodeType === Node.TEXT_NODE ? (_b = (_a = endNode.textContent) == null ? void 0 : _a.length) != null ? _b : 0 : endNode.childNodes.length;
8713
- if (endOffset !== len) return false;
8714
- let node = endNode;
8715
- while (node && node !== container) {
8716
- if (node.nextSibling) return false;
8717
- node = node.parentNode;
8718
- }
8719
- return node === container;
8720
- }
8721
- function isCaretOnEdgeLine(container, direction) {
8722
- const sel = window.getSelection();
8723
- if (!sel || sel.rangeCount === 0 || !sel.isCollapsed) return false;
8724
- const caretRange = sel.getRangeAt(0).cloneRange();
8725
- caretRange.collapse(true);
8726
- const caretRects = caretRange.getClientRects();
8727
- if (!caretRects.length) return true;
8728
- const caretRect = caretRects[0];
8729
- const containerRect = container.getBoundingClientRect();
8730
- const lineHeight = parseFloat(getComputedStyle(container).lineHeight) || 20;
8731
- if (direction === "up") {
8732
- return caretRect.top < containerRect.top + lineHeight;
8733
- } else {
8734
- return caretRect.bottom > containerRect.bottom - lineHeight;
8735
- }
8736
- }
8737
- var TablePlugin = {
8738
- name: "table",
8739
- keyBindings: {
8740
- Tab: (engine) => {
8741
- var _a;
8742
- const state = engine.getState();
8743
- const sel = state.selection;
8744
- if (!sel) return false;
8745
- const cellPos = findCellPosition(state.doc, sel.anchor.path);
8746
- if (!cellPos) return false;
8747
- const { tablePath, row, col } = cellPos;
8748
- const table = getNodeAtPath(state.doc, tablePath);
8749
- const { rows, cols } = getTableDimensions(table);
8750
- let nextRow = row;
8751
- let nextCol = col + 1;
8752
- while (nextRow < rows) {
8753
- if (nextCol >= cols) {
8754
- nextCol = 0;
8755
- nextRow++;
8756
- continue;
8757
- }
8758
- const c = getNodeAtPath(state.doc, [...tablePath, nextRow, nextCol]);
8759
- if (!((_a = c == null ? void 0 : c.attrs) == null ? void 0 : _a.covered)) break;
8760
- nextCol++;
8761
- }
8762
- if (nextRow >= rows) {
8763
- const newTable = insertTableRowAfter(table, rows - 1);
8764
- const newChildren = [...state.doc.children];
8765
- newChildren[tablePath[0]] = newTable;
8766
- const tempDoc = { type: "doc", children: newChildren };
8767
- const tr2 = createTransaction();
8768
- tr2.steps.push({ type: "delete_node", path: tablePath });
8769
- tr2.steps.push({
8770
- type: "insert_node",
8771
- parentPath: tablePath.length > 1 ? tablePath.slice(0, -1) : [],
8772
- index: tablePath[tablePath.length - 1],
8773
- node: newTable
8774
- });
8775
- const pos2 = getCellFirstPosition(tempDoc, tablePath, rows, 0);
8776
- if (pos2) tr2.steps.push(tr_setSelection(makeCollapsedSelection(pos2)));
8777
- engine.dispatch(tr2);
8778
- return true;
8696
+ function EditorCore({
8697
+ engine,
8698
+ placeholder = "Start writing...",
8699
+ className = "",
8700
+ readOnly = false,
8701
+ onHTMLChange,
8702
+ onJSONChange,
8703
+ onOpenLinkPopup,
8704
+ onUploadImage: _onUploadImage,
8705
+ onFocus: onFocusProp,
8706
+ onBlur: onBlurProp,
8707
+ hideToolbar = false,
8708
+ toolbarConfig
8709
+ }) {
8710
+ const containerRef = react.useRef(null);
8711
+ const isRenderingRef = react.useRef(false);
8712
+ const scrollCaretIntoView = react.useCallback(() => {
8713
+ var _a, _b;
8714
+ const sel = window.getSelection();
8715
+ if (!sel || sel.rangeCount === 0) return;
8716
+ const range = sel.getRangeAt(0).cloneRange();
8717
+ range.collapse(true);
8718
+ const caretRect = range.getBoundingClientRect();
8719
+ let rect = caretRect;
8720
+ if (!rect || rect.top === 0 && rect.bottom === 0 && rect.left === 0) {
8721
+ const node = range.startContainer;
8722
+ const el = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
8723
+ if (el) rect = el.getBoundingClientRect();
8724
+ }
8725
+ if (!rect || rect.bottom === 0) return;
8726
+ let scrollEl = (_b = (_a = containerRef.current) == null ? void 0 : _a.parentElement) != null ? _b : null;
8727
+ while (scrollEl) {
8728
+ const style = window.getComputedStyle(scrollEl);
8729
+ const overflow = style.overflow + style.overflowY;
8730
+ if (/auto|scroll/.test(overflow)) break;
8731
+ scrollEl = scrollEl.parentElement;
8732
+ }
8733
+ const PADDING = 24;
8734
+ if (scrollEl) {
8735
+ const containerRect = scrollEl.getBoundingClientRect();
8736
+ if (rect.bottom > containerRect.bottom - PADDING) {
8737
+ scrollEl.scrollBy({ top: rect.bottom - containerRect.bottom + PADDING, behavior: "smooth" });
8738
+ } else if (rect.top < containerRect.top + PADDING) {
8739
+ scrollEl.scrollBy({ top: rect.top - containerRect.top - PADDING, behavior: "smooth" });
8779
8740
  }
8780
- const pos = getCellFirstPosition(state.doc, tablePath, nextRow, nextCol);
8781
- if (!pos) return false;
8782
- const tr = createTransaction();
8783
- tr.steps.push(tr_setSelection(makeCollapsedSelection(pos)));
8784
- engine.dispatch(tr);
8785
- return true;
8786
- },
8787
- "ArrowRight": (engine) => {
8788
- var _a;
8789
- const state = engine.getState();
8790
- const sel = state.selection;
8791
- if (!sel) return false;
8792
- const cellPos = findCellPosition(state.doc, sel.anchor.path);
8793
- if (!cellPos) return false;
8794
- const { tablePath, row, col } = cellPos;
8795
- const cellPath = [...tablePath, row, col];
8796
- const cellEl = getCellDOMElement(JSON.stringify(cellPath));
8797
- if (!cellEl || !isCaretAtContainerEnd(cellEl)) return false;
8798
- const table = getNodeAtPath(state.doc, tablePath);
8799
- const { rows, cols } = getTableDimensions(table);
8800
- let nextRow = row;
8801
- let nextCol = col + 1;
8802
- while (nextRow < rows) {
8803
- if (nextCol >= cols) {
8804
- nextCol = 0;
8805
- nextRow++;
8806
- continue;
8807
- }
8808
- const c = getNodeAtPath(state.doc, [...tablePath, nextRow, nextCol]);
8809
- if (!((_a = c == null ? void 0 : c.attrs) == null ? void 0 : _a.covered)) break;
8810
- nextCol++;
8741
+ } else {
8742
+ if (rect.bottom > window.innerHeight - PADDING) {
8743
+ window.scrollBy({ top: rect.bottom - window.innerHeight + PADDING, behavior: "smooth" });
8744
+ } else if (rect.top < PADDING) {
8745
+ window.scrollBy({ top: rect.top - PADDING, behavior: "smooth" });
8811
8746
  }
8812
- if (nextRow >= rows) return true;
8813
- const pos = getCellFirstPosition(state.doc, tablePath, nextRow, nextCol);
8814
- if (!pos) return false;
8747
+ }
8748
+ }, []);
8749
+ const isComposingRef = react.useRef(false);
8750
+ const stateRef = react.useRef(engine.getState());
8751
+ const [selectedImagePath, setSelectedImagePath] = react.useState(null);
8752
+ const [findReplaceOpen, setFindReplaceOpen] = react.useState(false);
8753
+ const [findReplaceMode, setFindReplaceMode] = react.useState("find");
8754
+ const [linkPopupOpen, setLinkPopupOpen] = react.useState(false);
8755
+ const [isSourceMode, setIsSourceMode] = react.useState(false);
8756
+ const [sourceHTML, setSourceHTML] = react.useState("");
8757
+ const [editorHeight, setEditorHeight] = react.useState(null);
8758
+ const editorRootRef = react.useRef(null);
8759
+ const resizeDragRef = react.useRef(null);
8760
+ const [linkTooltip, setLinkTooltip] = react.useState(null);
8761
+ const linkTooltipTimerRef = react.useRef(null);
8762
+ const handleToggleSource = react.useCallback(() => {
8763
+ if (!isSourceMode) {
8764
+ const html = htmlSerializer.serialize(engine.getState().doc);
8765
+ setSourceHTML(prettyPrintHTML2(html));
8766
+ setIsSourceMode(true);
8767
+ } else {
8768
+ const newDoc = htmlSerializer.deserialize(sourceHTML);
8815
8769
  const tr = createTransaction();
8816
- tr.steps.push(tr_setSelection(makeCollapsedSelection(pos)));
8770
+ tr.steps.push(tr_replaceDoc(newDoc));
8817
8771
  engine.dispatch(tr);
8818
- return true;
8819
- },
8820
- "ArrowLeft": (engine) => {
8821
- var _a;
8822
- const state = engine.getState();
8823
- const sel = state.selection;
8824
- if (!sel) return false;
8825
- const cellPos = findCellPosition(state.doc, sel.anchor.path);
8826
- if (!cellPos) return false;
8827
- const { tablePath, row, col } = cellPos;
8828
- const cellPath = [...tablePath, row, col];
8829
- const cellEl = getCellDOMElement(JSON.stringify(cellPath));
8830
- if (!cellEl || !isCaretAtContainerStart(cellEl)) return false;
8831
- const table = getNodeAtPath(state.doc, tablePath);
8832
- const { cols } = getTableDimensions(table);
8833
- let prevRow = row;
8834
- let prevCol = col - 1;
8835
- while (prevRow >= 0) {
8836
- if (prevCol < 0) {
8837
- prevCol = cols - 1;
8838
- prevRow--;
8839
- continue;
8840
- }
8841
- const c = getNodeAtPath(state.doc, [...tablePath, prevRow, prevCol]);
8842
- if (!((_a = c == null ? void 0 : c.attrs) == null ? void 0 : _a.covered)) break;
8843
- prevCol--;
8844
- }
8845
- if (prevRow < 0) return true;
8846
- const pos = getCellLastPosition(state.doc, tablePath, prevRow, prevCol);
8847
- if (!pos) return false;
8848
- const tr = createTransaction();
8849
- tr.steps.push(tr_setSelection(makeCollapsedSelection(pos)));
8772
+ setIsSourceMode(false);
8773
+ }
8774
+ }, [isSourceMode, sourceHTML, engine]);
8775
+ react.useEffect(() => {
8776
+ const onMouseMove = (e) => {
8777
+ const drag = resizeDragRef.current;
8778
+ if (!drag) return;
8779
+ const newHeight = Math.max(120, drag.startHeight + (e.clientY - drag.startY));
8780
+ setEditorHeight(newHeight);
8781
+ };
8782
+ const onMouseUp = () => {
8783
+ resizeDragRef.current = null;
8784
+ };
8785
+ document.addEventListener("mousemove", onMouseMove);
8786
+ document.addEventListener("mouseup", onMouseUp);
8787
+ return () => {
8788
+ document.removeEventListener("mousemove", onMouseMove);
8789
+ document.removeEventListener("mouseup", onMouseUp);
8790
+ };
8791
+ }, []);
8792
+ const [tableContextMenu, setTableContextMenu] = react.useState(null);
8793
+ const [tableSelection, setTableSelection] = react.useState(null);
8794
+ const cellDragRef = react.useRef(null);
8795
+ const colResizeRef = react.useRef(null);
8796
+ const state = useEditorState(engine);
8797
+ const inTableCellPos = state.selection ? findCellPosition(state.doc, state.selection.anchor.path) : null;
8798
+ react.useLayoutEffect(() => {
8799
+ stateRef.current = state;
8800
+ }, [state]);
8801
+ react.useLayoutEffect(() => {
8802
+ const container = containerRef.current;
8803
+ if (!container) return;
8804
+ isRenderingRef.current = true;
8805
+ renderDocument(state.doc, container);
8806
+ if (!container.contains(document.activeElement)) {
8807
+ container.focus({ preventScroll: true });
8808
+ }
8809
+ if (state.selection) {
8810
+ restoreSelection(container, state.selection);
8811
+ scrollCaretIntoView();
8812
+ }
8813
+ isRenderingRef.current = false;
8814
+ if (onHTMLChange) {
8815
+ onHTMLChange(htmlSerializer.serialize(state.doc));
8816
+ }
8817
+ if (onJSONChange) {
8818
+ onJSONChange(jsonSerializer.serialize(state.doc));
8819
+ }
8820
+ }, [state.doc]);
8821
+ react.useLayoutEffect(() => {
8822
+ const container = containerRef.current;
8823
+ if (!container || !state.selection) return;
8824
+ restoreSelection(container, state.selection);
8825
+ scrollCaretIntoView();
8826
+ }, [state.selection]);
8827
+ react.useEffect(() => {
8828
+ const container = containerRef.current;
8829
+ if (!container || readOnly) return;
8830
+ return attachClipboardHandlers(container, engine);
8831
+ }, [engine, readOnly]);
8832
+ react.useEffect(() => {
8833
+ const container = containerRef.current;
8834
+ if (!container) return;
8835
+ container.querySelectorAll(".editor-cell-selected").forEach((el) => {
8836
+ el.classList.remove("editor-cell-selected");
8837
+ });
8838
+ if (!tableSelection) return;
8839
+ const { tablePath, anchorCell, focusCell } = tableSelection;
8840
+ const minRow = Math.min(anchorCell[0], focusCell[0]);
8841
+ const maxRow = Math.max(anchorCell[0], focusCell[0]);
8842
+ const minCol = Math.min(anchorCell[1], focusCell[1]);
8843
+ const maxCol = Math.max(anchorCell[1], focusCell[1]);
8844
+ for (let r = minRow; r <= maxRow; r++) {
8845
+ for (let c = minCol; c <= maxCol; c++) {
8846
+ const cellPath = [...tablePath, r, c];
8847
+ const td = container.querySelector(
8848
+ `[data-block-path="${JSON.stringify(cellPath)}"]`
8849
+ );
8850
+ if (td) td.classList.add("editor-cell-selected");
8851
+ }
8852
+ }
8853
+ }, [tableSelection, state.doc]);
8854
+ react.useEffect(() => {
8855
+ const container = containerRef.current;
8856
+ if (!container) return;
8857
+ container.querySelectorAll(".editor-cell-active").forEach((el) => {
8858
+ el.classList.remove("editor-cell-active");
8859
+ });
8860
+ if (!inTableCellPos) {
8861
+ setTableSelection(null);
8862
+ return;
8863
+ }
8864
+ if (tableSelection) {
8865
+ const { anchorCell, focusCell, tablePath } = tableSelection;
8866
+ const sameTable = JSON.stringify(tablePath) === JSON.stringify(inTableCellPos.tablePath);
8867
+ const singleCell = anchorCell[0] === focusCell[0] && anchorCell[1] === focusCell[1];
8868
+ if (!sameTable || singleCell) {
8869
+ setTableSelection(null);
8870
+ }
8871
+ }
8872
+ const cellPath = [...inTableCellPos.tablePath, inTableCellPos.row, inTableCellPos.col];
8873
+ const td = container.querySelector(
8874
+ `[data-block-path="${JSON.stringify(cellPath)}"]`
8875
+ );
8876
+ if (td) td.classList.add("editor-cell-active");
8877
+ }, [inTableCellPos, state.doc]);
8878
+ react.useEffect(() => {
8879
+ const onSelectionChange = () => {
8880
+ if (isRenderingRef.current) return;
8881
+ const container = containerRef.current;
8882
+ if (!container) return;
8883
+ if (!isSelectionInContainer(container)) return;
8884
+ const captured = captureSelection(container);
8885
+ if (!captured) return;
8886
+ const currentSelection = engine.getState().selection;
8887
+ if (currentSelection && JSON.stringify(currentSelection.anchor) === JSON.stringify(captured.anchor) && JSON.stringify(currentSelection.focus) === JSON.stringify(captured.focus)) {
8888
+ return;
8889
+ }
8890
+ const tr = createTransaction();
8891
+ tr.steps.push(tr_setSelection(captured));
8892
+ engine.dispatch(tr);
8893
+ };
8894
+ document.addEventListener(
8895
+ "selectionchange",
8896
+ onSelectionChange
8897
+ );
8898
+ return () => {
8899
+ document.removeEventListener(
8900
+ "selectionchange",
8901
+ onSelectionChange
8902
+ );
8903
+ };
8904
+ }, [engine]);
8905
+ const handleFocus = react.useCallback(() => {
8906
+ const currentState = engine.getState();
8907
+ if (!currentState.selection) {
8908
+ const tr = createTransaction();
8909
+ tr.steps.push(
8910
+ tr_setSelection(
8911
+ makeCollapsedSelection({ path: [0], offset: 0 })
8912
+ )
8913
+ );
8914
+ engine.dispatch(tr);
8915
+ }
8916
+ onFocusProp == null ? void 0 : onFocusProp();
8917
+ }, [engine, onFocusProp]);
8918
+ const handleBlur = react.useCallback(() => {
8919
+ onBlurProp == null ? void 0 : onBlurProp();
8920
+ }, [onBlurProp]);
8921
+ const handleKeyDown = react.useCallback(
8922
+ (e) => {
8923
+ var _a, _b, _c;
8924
+ if (readOnly) return;
8925
+ if ((e.ctrlKey || e.metaKey) && e.key === "f") {
8926
+ e.preventDefault();
8927
+ setFindReplaceMode("find");
8928
+ setFindReplaceOpen(true);
8929
+ return;
8930
+ }
8931
+ if ((e.ctrlKey || e.metaKey) && e.key === "h") {
8932
+ e.preventDefault();
8933
+ setFindReplaceMode("replace");
8934
+ setFindReplaceOpen(true);
8935
+ return;
8936
+ }
8937
+ if ((e.ctrlKey || e.metaKey) && e.key === "k") {
8938
+ e.preventDefault();
8939
+ setLinkPopupOpen(true);
8940
+ onOpenLinkPopup == null ? void 0 : onOpenLinkPopup();
8941
+ return;
8942
+ }
8943
+ if ((e.ctrlKey || e.metaKey) && (e.key === "a" || e.key === "A")) {
8944
+ e.preventDefault();
8945
+ const container = containerRef.current;
8946
+ if (container) {
8947
+ const spans = container.querySelectorAll("[data-path]");
8948
+ const first = spans[0];
8949
+ const last = spans[spans.length - 1];
8950
+ if (first && last) {
8951
+ const sel = window.getSelection();
8952
+ const range = document.createRange();
8953
+ range.setStart((_a = leafTextNode2(first, "first")) != null ? _a : first, 0);
8954
+ const lastLeaf = leafTextNode2(last, "last");
8955
+ range.setEnd(lastLeaf != null ? lastLeaf : last, (_c = (_b = lastLeaf == null ? void 0 : lastLeaf.textContent) == null ? void 0 : _b.length) != null ? _c : 0);
8956
+ sel == null ? void 0 : sel.removeAllRanges();
8957
+ sel == null ? void 0 : sel.addRange(range);
8958
+ }
8959
+ }
8960
+ return;
8961
+ }
8962
+ if (selectedImagePath && (e.key === "Delete" || e.key === "Backspace")) {
8963
+ e.preventDefault();
8964
+ deleteImageAtPath(selectedImagePath)(engine);
8965
+ setSelectedImagePath(null);
8966
+ return;
8967
+ }
8968
+ if (selectedImagePath) setSelectedImagePath(null);
8969
+ if (e.key === "Tab") {
8970
+ const { doc, selection: sel } = engine.getState();
8971
+ if (sel) {
8972
+ const bp = findContentBlockPath(doc, sel.anchor.path);
8973
+ const block = bp ? getNodeAtPath(doc, bp) : null;
8974
+ if ((block == null ? void 0 : block.type) === "code_block") {
8975
+ e.preventDefault();
8976
+ insertText(" ")(engine);
8977
+ return;
8978
+ }
8979
+ if ((block == null ? void 0 : block.type) === "list_item") {
8980
+ e.preventDefault();
8981
+ if (e.shiftKey) {
8982
+ outdentListItem(engine);
8983
+ } else {
8984
+ indentListItem(engine);
8985
+ }
8986
+ return;
8987
+ }
8988
+ if (findCellPosition(doc, sel.anchor.path)) {
8989
+ e.preventDefault();
8990
+ }
8991
+ }
8992
+ }
8993
+ const handled = engine.handleKeyDown(
8994
+ e.nativeEvent
8995
+ );
8996
+ if (handled) return;
8997
+ if (e.key === "Enter" && !e.shiftKey) {
8998
+ e.preventDefault();
8999
+ handleEnter(engine);
9000
+ return;
9001
+ }
9002
+ if (e.key === "Backspace") {
9003
+ e.preventDefault();
9004
+ handleBackspace(engine);
9005
+ return;
9006
+ }
9007
+ if (e.key === "Delete") {
9008
+ e.preventDefault();
9009
+ handleDelete(engine);
9010
+ return;
9011
+ }
9012
+ if (e.key === "Enter" && e.shiftKey) {
9013
+ e.preventDefault();
9014
+ insertText("\n")(engine);
9015
+ return;
9016
+ }
9017
+ },
9018
+ [engine, readOnly, onOpenLinkPopup, selectedImagePath]
9019
+ );
9020
+ react.useEffect(() => {
9021
+ const container = containerRef.current;
9022
+ if (!container || readOnly) return;
9023
+ const onBeforeInput = (e) => {
9024
+ if (e.isComposing || isComposingRef.current) return;
9025
+ switch (e.inputType) {
9026
+ case "insertText":
9027
+ case "insertReplacementText": {
9028
+ if (!e.data) return;
9029
+ e.preventDefault();
9030
+ insertText(e.data)(engine);
9031
+ return;
9032
+ }
9033
+ case "insertParagraph": {
9034
+ e.preventDefault();
9035
+ handleEnter(engine);
9036
+ return;
9037
+ }
9038
+ case "insertLineBreak": {
9039
+ e.preventDefault();
9040
+ insertText("\n")(engine);
9041
+ return;
9042
+ }
9043
+ case "deleteContentBackward":
9044
+ case "deleteWordBackward":
9045
+ case "deleteSoftLineBackward": {
9046
+ e.preventDefault();
9047
+ handleBackspace(engine);
9048
+ return;
9049
+ }
9050
+ case "insertFromPaste":
9051
+ case "insertFromDrop":
9052
+ return;
9053
+ default:
9054
+ e.preventDefault();
9055
+ }
9056
+ };
9057
+ container.addEventListener("beforeinput", onBeforeInput);
9058
+ return () => container.removeEventListener("beforeinput", onBeforeInput);
9059
+ }, [engine, readOnly]);
9060
+ react.useEffect(() => {
9061
+ const container = containerRef.current;
9062
+ if (!container || readOnly) return;
9063
+ const onCompositionStart = () => {
9064
+ isComposingRef.current = true;
9065
+ };
9066
+ const onCompositionEnd = (e) => {
9067
+ isComposingRef.current = false;
9068
+ if (e.data) insertText(e.data)(engine);
9069
+ };
9070
+ container.addEventListener("compositionstart", onCompositionStart);
9071
+ container.addEventListener("compositionend", onCompositionEnd);
9072
+ return () => {
9073
+ container.removeEventListener("compositionstart", onCompositionStart);
9074
+ container.removeEventListener("compositionend", onCompositionEnd);
9075
+ };
9076
+ }, [engine, readOnly]);
9077
+ react.useEffect(() => {
9078
+ const container = containerRef.current;
9079
+ if (!container) return;
9080
+ const onMouseOver = (e) => {
9081
+ var _a;
9082
+ const anchor = e.target.closest("a.editor-link");
9083
+ if (!anchor) return;
9084
+ const href = (_a = anchor.getAttribute("href")) != null ? _a : "";
9085
+ if (!href) return;
9086
+ if (linkTooltipTimerRef.current) clearTimeout(linkTooltipTimerRef.current);
9087
+ linkTooltipTimerRef.current = setTimeout(() => {
9088
+ const rect = anchor.getBoundingClientRect();
9089
+ const containerRect = container.getBoundingClientRect();
9090
+ setLinkTooltip({
9091
+ href,
9092
+ x: rect.left - containerRect.left,
9093
+ y: rect.bottom - containerRect.top + 6
9094
+ });
9095
+ }, 200);
9096
+ };
9097
+ const onMouseOut = (e) => {
9098
+ const anchor = e.target.closest("a.editor-link");
9099
+ if (!anchor) return;
9100
+ if (linkTooltipTimerRef.current) clearTimeout(linkTooltipTimerRef.current);
9101
+ setLinkTooltip(null);
9102
+ };
9103
+ const onClick = (e) => {
9104
+ if (!(e.ctrlKey || e.metaKey)) return;
9105
+ const anchor = e.target.closest("a.editor-link");
9106
+ if (!anchor) return;
9107
+ e.preventDefault();
9108
+ const href = anchor.getAttribute("href");
9109
+ if (href) window.open(href, "_blank", "noopener,noreferrer");
9110
+ };
9111
+ container.addEventListener("mouseover", onMouseOver);
9112
+ container.addEventListener("mouseout", onMouseOut);
9113
+ container.addEventListener("click", onClick);
9114
+ return () => {
9115
+ container.removeEventListener("mouseover", onMouseOver);
9116
+ container.removeEventListener("mouseout", onMouseOut);
9117
+ container.removeEventListener("click", onClick);
9118
+ if (linkTooltipTimerRef.current) clearTimeout(linkTooltipTimerRef.current);
9119
+ };
9120
+ }, []);
9121
+ react.useEffect(() => {
9122
+ const onMouseMove = (e) => {
9123
+ const drag = colResizeRef.current;
9124
+ if (!drag) return;
9125
+ const delta = e.clientX - drag.startX;
9126
+ const newWidth = Math.max(40, drag.startWidth + delta);
9127
+ const container = containerRef.current;
9128
+ if (container) {
9129
+ const colEl = container.querySelector(
9130
+ `col[data-col-index="${drag.colIndex}"]`
9131
+ );
9132
+ if (colEl) colEl.style.width = `${newWidth}px`;
9133
+ }
9134
+ };
9135
+ const onMouseUp = (e) => {
9136
+ const drag = colResizeRef.current;
9137
+ if (!drag) return;
9138
+ const delta = e.clientX - drag.startX;
9139
+ const newWidth = Math.max(40, drag.startWidth + delta);
9140
+ colResizeRef.current = null;
9141
+ setColumnWidth(drag.tablePath, drag.colIndex, newWidth)(engine);
9142
+ };
9143
+ document.addEventListener("mousemove", onMouseMove);
9144
+ document.addEventListener("mouseup", onMouseUp);
9145
+ return () => {
9146
+ document.removeEventListener("mousemove", onMouseMove);
9147
+ document.removeEventListener("mouseup", onMouseUp);
9148
+ };
9149
+ }, [engine]);
9150
+ react.useEffect(() => {
9151
+ const onMouseMove = (e) => {
9152
+ var _a, _b;
9153
+ const drag = cellDragRef.current;
9154
+ if (!drag) return;
9155
+ const td = e.target.closest("[data-cell-row]");
9156
+ if (!td) return;
9157
+ const row = parseInt((_a = td.dataset.cellRow) != null ? _a : "0");
9158
+ const col = parseInt((_b = td.dataset.cellCol) != null ? _b : "0");
9159
+ const tdTablePath = td.dataset.cellTablePath ? JSON.parse(td.dataset.cellTablePath) : null;
9160
+ if (!tdTablePath || JSON.stringify(tdTablePath) !== JSON.stringify(drag.tablePath)) return;
9161
+ setTableSelection({
9162
+ tablePath: drag.tablePath,
9163
+ anchorCell: [drag.startRow, drag.startCol],
9164
+ focusCell: [row, col]
9165
+ });
9166
+ };
9167
+ const onMouseUp = () => {
9168
+ cellDragRef.current = null;
9169
+ };
9170
+ document.addEventListener("mousemove", onMouseMove);
9171
+ document.addEventListener("mouseup", onMouseUp);
9172
+ return () => {
9173
+ document.removeEventListener("mousemove", onMouseMove);
9174
+ document.removeEventListener("mouseup", onMouseUp);
9175
+ };
9176
+ }, []);
9177
+ const handleContextMenu = react.useCallback(
9178
+ (e) => {
9179
+ var _a, _b, _c, _d, _e, _f;
9180
+ const td = e.target.closest("[data-cell-row]");
9181
+ if (!td) return;
9182
+ e.preventDefault();
9183
+ const row = parseInt((_a = td.dataset.cellRow) != null ? _a : "0");
9184
+ const col = parseInt((_b = td.dataset.cellCol) != null ? _b : "0");
9185
+ const tablePath = td.dataset.cellTablePath ? JSON.parse(td.dataset.cellTablePath) : null;
9186
+ if (!tablePath) return;
9187
+ const cellNode = getNodeAtPath(
9188
+ engine.getState().doc,
9189
+ [...tablePath, row, col]
9190
+ );
9191
+ const isMerged = !!cellNode && (((_d = (_c = cellNode.attrs) == null ? void 0 : _c.colspan) != null ? _d : 1) > 1 || ((_f = (_e = cellNode.attrs) == null ? void 0 : _e.rowspan) != null ? _f : 1) > 1);
9192
+ setTableContextMenu({ x: e.clientX, y: e.clientY, tablePath, row, col, isMerged });
9193
+ },
9194
+ [engine]
9195
+ );
9196
+ const handleMouseDown = react.useCallback(
9197
+ (e) => {
9198
+ var _a, _b, _c, _d;
9199
+ if (readOnly) return;
9200
+ const target = e.target;
9201
+ if (target.dataset.resizeImagePath) {
9202
+ e.preventDefault();
9203
+ const imagePath = JSON.parse(target.dataset.resizeImagePath);
9204
+ const corner = (_a = target.dataset.resizeImagePos) != null ? _a : "se";
9205
+ const container = containerRef.current;
9206
+ let startWidth = 300;
9207
+ if (container) {
9208
+ const fig2 = container.querySelector(`[data-image-path="${JSON.stringify(imagePath)}"]`);
9209
+ const img = fig2 == null ? void 0 : fig2.querySelector("img");
9210
+ if (img) startWidth = img.getBoundingClientRect().width || 300;
9211
+ }
9212
+ imageResizeRef.current = {
9213
+ imagePath,
9214
+ corner,
9215
+ startX: e.clientX,
9216
+ startY: e.clientY,
9217
+ startWidth,
9218
+ startHeight: 0
9219
+ };
9220
+ return;
9221
+ }
9222
+ const fig = target.closest("[data-image-path]");
9223
+ if (fig == null ? void 0 : fig.dataset.imagePath) {
9224
+ e.preventDefault();
9225
+ setSelectedImagePath(JSON.parse(fig.dataset.imagePath));
9226
+ return;
9227
+ }
9228
+ setSelectedImagePath(null);
9229
+ if (target.dataset.resizeTable) {
9230
+ e.preventDefault();
9231
+ const tablePath = JSON.parse(target.dataset.resizeTable);
9232
+ const colIndex = parseInt((_b = target.dataset.resizeCol) != null ? _b : "0");
9233
+ const container = containerRef.current;
9234
+ let startWidth = 120;
9235
+ if (container) {
9236
+ const colEl = container.querySelector(
9237
+ `col[data-col-index="${colIndex}"]`
9238
+ );
9239
+ if (colEl) startWidth = colEl.getBoundingClientRect().width || 120;
9240
+ }
9241
+ colResizeRef.current = { tablePath, colIndex, startX: e.clientX, startWidth };
9242
+ return;
9243
+ }
9244
+ const td = target.closest("[data-cell-row]");
9245
+ if (!td) setTableSelection(null);
9246
+ if (td && !readOnly) {
9247
+ const row = parseInt((_c = td.dataset.cellRow) != null ? _c : "0");
9248
+ const col = parseInt((_d = td.dataset.cellCol) != null ? _d : "0");
9249
+ const tdTablePath = td.dataset.cellTablePath ? JSON.parse(td.dataset.cellTablePath) : null;
9250
+ if (tdTablePath) {
9251
+ cellDragRef.current = { tablePath: tdTablePath, startRow: row, startCol: col };
9252
+ setTableSelection({
9253
+ tablePath: tdTablePath,
9254
+ anchorCell: [row, col],
9255
+ focusCell: [row, col]
9256
+ });
9257
+ }
9258
+ }
9259
+ const checkTarget = target.dataset.checkPath ? target : target.closest("[data-check-path]");
9260
+ if (checkTarget == null ? void 0 : checkTarget.dataset.checkPath) {
9261
+ e.preventDefault();
9262
+ toggleCheckItemAt(JSON.parse(checkTarget.dataset.checkPath))(engine);
9263
+ }
9264
+ },
9265
+ [engine, readOnly]
9266
+ );
9267
+ const handleClick = react.useCallback(() => {
9268
+ const container = containerRef.current;
9269
+ if (!container) return;
9270
+ const selection = captureSelection(container);
9271
+ if (!selection) return;
9272
+ const tr = createTransaction();
9273
+ tr.steps.push(
9274
+ tr_setSelection(selection)
9275
+ );
9276
+ engine.dispatch(tr);
9277
+ }, [engine]);
9278
+ const imageResizeRef = react.useRef(null);
9279
+ react.useEffect(() => {
9280
+ const onMouseMove = (e) => {
9281
+ const drag = imageResizeRef.current;
9282
+ if (!drag) return;
9283
+ const deltaX = e.clientX - drag.startX;
9284
+ const isRight = drag.corner === "ne" || drag.corner === "se";
9285
+ const newWidth = Math.max(60, drag.startWidth + (isRight ? deltaX : -deltaX));
9286
+ const container = containerRef.current;
9287
+ if (container) {
9288
+ const fig = container.querySelector(
9289
+ `[data-image-path="${JSON.stringify(drag.imagePath)}"]`
9290
+ );
9291
+ const img = fig == null ? void 0 : fig.querySelector("img");
9292
+ if (img) img.style.width = `${newWidth}px`;
9293
+ }
9294
+ };
9295
+ const onMouseUp = (e) => {
9296
+ const drag = imageResizeRef.current;
9297
+ if (!drag) return;
9298
+ const deltaX = e.clientX - drag.startX;
9299
+ const isRight = drag.corner === "ne" || drag.corner === "se";
9300
+ const newWidth = Math.max(60, drag.startWidth + (isRight ? deltaX : -deltaX));
9301
+ imageResizeRef.current = null;
9302
+ setImageAttr(drag.imagePath, { width: newWidth })(engine);
9303
+ };
9304
+ document.addEventListener("mousemove", onMouseMove);
9305
+ document.addEventListener("mouseup", onMouseUp);
9306
+ return () => {
9307
+ document.removeEventListener("mousemove", onMouseMove);
9308
+ document.removeEventListener("mouseup", onMouseUp);
9309
+ };
9310
+ }, [engine]);
9311
+ react.useEffect(() => {
9312
+ const container = containerRef.current;
9313
+ if (!container) return;
9314
+ container.querySelectorAll("[data-image-path]").forEach((el) => {
9315
+ el.removeAttribute("data-selected");
9316
+ });
9317
+ if (selectedImagePath) {
9318
+ const fig = container.querySelector(
9319
+ `[data-image-path="${JSON.stringify(selectedImagePath)}"]`
9320
+ );
9321
+ if (fig) fig.setAttribute("data-selected", "true");
9322
+ }
9323
+ }, [selectedImagePath, state.doc]);
9324
+ react.useEffect(() => {
9325
+ const container = containerRef.current;
9326
+ if (!container || readOnly) return;
9327
+ const onDragOver = (e) => {
9328
+ var _a;
9329
+ if ((_a = e.dataTransfer) == null ? void 0 : _a.types.includes("Files")) e.preventDefault();
9330
+ };
9331
+ const onDrop = (e) => {
9332
+ var _a;
9333
+ e.preventDefault();
9334
+ const file = (_a = e.dataTransfer) == null ? void 0 : _a.files[0];
9335
+ if (!file || !file.type.startsWith("image/")) return;
9336
+ const blobUrl = URL.createObjectURL(file);
9337
+ insertImage(blobUrl, file.name.replace(/\.[^.]+$/, ""))(engine);
9338
+ };
9339
+ container.addEventListener("dragover", onDragOver);
9340
+ container.addEventListener("drop", onDrop);
9341
+ return () => {
9342
+ container.removeEventListener("dragover", onDragOver);
9343
+ container.removeEventListener("drop", onDrop);
9344
+ };
9345
+ }, [engine, readOnly]);
9346
+ const isVisualEmpty = state.doc.children.length === 1 && state.doc.children[0].type === "paragraph" && state.doc.children[0].children.length === 0;
9347
+ return /* @__PURE__ */ jsxRuntime.jsxs(
9348
+ "div",
9349
+ {
9350
+ ref: editorRootRef,
9351
+ className: `editor-root relative flex flex-col ${className}`,
9352
+ style: editorHeight ? { minHeight: editorHeight } : void 0,
9353
+ children: [
9354
+ !readOnly && !hideToolbar && /* @__PURE__ */ jsxRuntime.jsx(
9355
+ Toolbar,
9356
+ {
9357
+ engine,
9358
+ onFindReplace: (mode) => {
9359
+ setFindReplaceMode(mode);
9360
+ setFindReplaceOpen(true);
9361
+ },
9362
+ linkPopupOpen,
9363
+ onLinkPopupClose: () => setLinkPopupOpen(false),
9364
+ isSourceMode,
9365
+ onToggleSource: handleToggleSource,
9366
+ toolbarConfig
9367
+ }
9368
+ ),
9369
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative", children: isSourceMode ? /* @__PURE__ */ jsxRuntime.jsx(
9370
+ "textarea",
9371
+ {
9372
+ "aria-label": "HTML source editor",
9373
+ value: sourceHTML,
9374
+ onChange: (e) => {
9375
+ setSourceHTML(e.target.value);
9376
+ const t = e.target;
9377
+ t.style.height = "auto";
9378
+ t.style.height = t.scrollHeight + "px";
9379
+ },
9380
+ ref: (el) => {
9381
+ if (el) {
9382
+ el.style.height = "auto";
9383
+ el.style.height = el.scrollHeight + "px";
9384
+ }
9385
+ },
9386
+ spellCheck: false,
9387
+ className: "w-full block px-6 py-4 font-mono text-sm leading-relaxed bg-gray-950 text-green-300 outline-none resize-none border-0 overflow-hidden"
9388
+ }
9389
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9390
+ isVisualEmpty && /* @__PURE__ */ jsxRuntime.jsx(
9391
+ "div",
9392
+ {
9393
+ className: "\r\n absolute\r\n top-6\r\n left-6\r\n pointer-events-none\r\n select-none\r\n text-base\r\n text-gray-400\r\n \r\n ",
9394
+ "aria-hidden": "true",
9395
+ children: placeholder
9396
+ }
9397
+ ),
9398
+ /* @__PURE__ */ jsxRuntime.jsx(
9399
+ "div",
9400
+ {
9401
+ ref: containerRef,
9402
+ contentEditable: !readOnly,
9403
+ suppressContentEditableWarning: true,
9404
+ role: "textbox",
9405
+ "aria-multiline": "true",
9406
+ "aria-label": "Rich text editor",
9407
+ spellCheck: true,
9408
+ onMouseDown: handleMouseDown,
9409
+ onContextMenu: handleContextMenu,
9410
+ onFocus: handleFocus,
9411
+ onBlur: handleBlur,
9412
+ onClick: handleClick,
9413
+ onKeyDown: handleKeyDown,
9414
+ className: [
9415
+ "editor-canvas",
9416
+ "min-h-[300px]",
9417
+ "px-6",
9418
+ "py-4",
9419
+ "outline-none",
9420
+ "text-base",
9421
+ "leading-relaxed",
9422
+ "text-gray-900",
9423
+ "",
9424
+ readOnly ? "cursor-default" : "cursor-text"
9425
+ ].join(" ")
9426
+ }
9427
+ ),
9428
+ linkTooltip && /* @__PURE__ */ jsxRuntime.jsxs(
9429
+ "div",
9430
+ {
9431
+ role: "tooltip",
9432
+ style: { left: linkTooltip.x, top: linkTooltip.y },
9433
+ className: "absolute z-50 pointer-events-none flex items-center gap-1.5 bg-gray-900 text-white text-xs px-2.5 py-1.5 rounded shadow-lg max-w-xs",
9434
+ children: [
9435
+ /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "11", height: "11", viewBox: "0 0 16 16", fill: "none", className: "shrink-0 opacity-70", "aria-hidden": "true", children: [
9436
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6.5 3.5H4A2.5 2.5 0 0 0 4 8.5h2", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" }),
9437
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9.5 3.5H12A2.5 2.5 0 0 1 12 8.5h-2", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" }),
9438
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5.5 6h5", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" })
9439
+ ] }),
9440
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: linkTooltip.href }),
9441
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 opacity-50 ml-1", children: "Ctrl+click to open" })
9442
+ ]
9443
+ }
9444
+ )
9445
+ ] }) }),
9446
+ inTableCellPos && !readOnly && /* @__PURE__ */ jsxRuntime.jsx(
9447
+ TableToolbar,
9448
+ {
9449
+ engine,
9450
+ tablePath: inTableCellPos.tablePath,
9451
+ cellPos: { row: inTableCellPos.row, col: inTableCellPos.col },
9452
+ tableSelection,
9453
+ editorContainer: containerRef
9454
+ }
9455
+ ),
9456
+ tableContextMenu && /* @__PURE__ */ jsxRuntime.jsx(
9457
+ TableContextMenu,
9458
+ {
9459
+ x: tableContextMenu.x,
9460
+ y: tableContextMenu.y,
9461
+ tablePath: tableContextMenu.tablePath,
9462
+ row: tableContextMenu.row,
9463
+ col: tableContextMenu.col,
9464
+ isMerged: tableContextMenu.isMerged,
9465
+ tableSelection,
9466
+ engine,
9467
+ onClose: () => setTableContextMenu(null)
9468
+ }
9469
+ ),
9470
+ findReplaceOpen && /* @__PURE__ */ jsxRuntime.jsx(
9471
+ FindReplaceModal,
9472
+ {
9473
+ engine,
9474
+ editorContainer: containerRef,
9475
+ initialMode: findReplaceMode,
9476
+ onClose: () => setFindReplaceOpen(false)
9477
+ }
9478
+ ),
9479
+ selectedImagePath && !readOnly && /* @__PURE__ */ jsxRuntime.jsx(
9480
+ ImageToolbar,
9481
+ {
9482
+ engine,
9483
+ imagePath: selectedImagePath,
9484
+ editorContainer: containerRef,
9485
+ onClose: () => setSelectedImagePath(null)
9486
+ }
9487
+ ),
9488
+ !readOnly && /* @__PURE__ */ jsxRuntime.jsx(
9489
+ "div",
9490
+ {
9491
+ title: "Drag to resize",
9492
+ onMouseDown: (e) => {
9493
+ e.preventDefault();
9494
+ const rootEl = editorRootRef.current;
9495
+ if (!rootEl) return;
9496
+ resizeDragRef.current = {
9497
+ startY: e.clientY,
9498
+ startHeight: rootEl.getBoundingClientRect().height
9499
+ };
9500
+ },
9501
+ className: "absolute bottom-0 right-0 w-4 h-4 cursor-s-resize flex items-end justify-end pr-0.5 pb-0.5 select-none",
9502
+ "aria-hidden": "true",
9503
+ children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "8", height: "8", viewBox: "0 0 8 8", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M7 1L1 7M7 4L4 7M7 7L7 7", stroke: "#9ca3af", strokeWidth: "1.2", strokeLinecap: "round" }) })
9504
+ }
9505
+ )
9506
+ ]
9507
+ }
9508
+ );
9509
+ }
9510
+ var BLOCK_TAGS2 = "p|h[1-6]|ul|ol|li|blockquote|pre|figure|table|thead|tbody|tr|th|td";
9511
+ var _OPEN_ONLY_RE2 = new RegExp(`^<(${BLOCK_TAGS2})(?:\\s[^>]*)?>$`, "i");
9512
+ var _CLOSE_ONLY_RE2 = new RegExp(`^<\\/(${BLOCK_TAGS2})>$`, "i");
9513
+ var _HR_RE2 = /^<hr(\s[^>]*)?\/?>$/i;
9514
+ function leafTextNode2(el, end) {
9515
+ let node = el;
9516
+ while (node.hasChildNodes()) {
9517
+ node = end === "first" ? node.firstChild : node.lastChild;
9518
+ }
9519
+ return node.nodeType === Node.TEXT_NODE ? node : null;
9520
+ }
9521
+ function prettyPrintHTML2(html) {
9522
+ const blockOpen = new RegExp(`(<(?:${BLOCK_TAGS2})(?:\\s[^>]*)?>)`, "gi");
9523
+ const blockClose = new RegExp(`(<\\/(?:${BLOCK_TAGS2})>)`, "gi");
9524
+ const spread = html.replace(blockOpen, "\n$1").replace(blockClose, "$1\n").replace(/<hr(\s[^>]*)?\/?>(\s*)/gi, "\n<hr />\n");
9525
+ let depth = 0;
9526
+ const result = [];
9527
+ for (const raw of spread.split("\n")) {
9528
+ const line = raw.trim();
9529
+ if (!line) continue;
9530
+ if (_HR_RE2.test(line)) {
9531
+ result.push(" ".repeat(depth) + line);
9532
+ continue;
9533
+ }
9534
+ if (_CLOSE_ONLY_RE2.test(line)) {
9535
+ depth = Math.max(0, depth - 1);
9536
+ result.push(" ".repeat(depth) + line);
9537
+ continue;
9538
+ }
9539
+ if (_OPEN_ONLY_RE2.test(line)) {
9540
+ result.push(" ".repeat(depth) + line);
9541
+ depth++;
9542
+ continue;
9543
+ }
9544
+ result.push(" ".repeat(depth) + line);
9545
+ }
9546
+ return result.join("\n");
9547
+ }
9548
+
9549
+ // src/editor/table/TablePlugin.ts
9550
+ function getCellDOMElement(cellPathJson) {
9551
+ return document.querySelector(`[data-block-path='${cellPathJson}']`);
9552
+ }
9553
+ function isCaretAtContainerStart(container) {
9554
+ const sel = window.getSelection();
9555
+ if (!sel || sel.rangeCount === 0 || !sel.isCollapsed) return false;
9556
+ const range = sel.getRangeAt(0);
9557
+ if (range.startOffset !== 0) return false;
9558
+ let node = range.startContainer;
9559
+ while (node && node !== container) {
9560
+ if (node.previousSibling) return false;
9561
+ node = node.parentNode;
9562
+ }
9563
+ return node === container;
9564
+ }
9565
+ function isCaretAtContainerEnd(container) {
9566
+ var _a, _b;
9567
+ const sel = window.getSelection();
9568
+ if (!sel || sel.rangeCount === 0 || !sel.isCollapsed) return false;
9569
+ const range = sel.getRangeAt(0);
9570
+ const endNode = range.endContainer;
9571
+ const endOffset = range.endOffset;
9572
+ const len = endNode.nodeType === Node.TEXT_NODE ? (_b = (_a = endNode.textContent) == null ? void 0 : _a.length) != null ? _b : 0 : endNode.childNodes.length;
9573
+ if (endOffset !== len) return false;
9574
+ let node = endNode;
9575
+ while (node && node !== container) {
9576
+ if (node.nextSibling) return false;
9577
+ node = node.parentNode;
9578
+ }
9579
+ return node === container;
9580
+ }
9581
+ function isCaretOnEdgeLine(container, direction) {
9582
+ const sel = window.getSelection();
9583
+ if (!sel || sel.rangeCount === 0 || !sel.isCollapsed) return false;
9584
+ const caretRange = sel.getRangeAt(0).cloneRange();
9585
+ caretRange.collapse(true);
9586
+ const caretRects = caretRange.getClientRects();
9587
+ if (!caretRects.length) return true;
9588
+ const caretRect = caretRects[0];
9589
+ const containerRect = container.getBoundingClientRect();
9590
+ const lineHeight = parseFloat(getComputedStyle(container).lineHeight) || 20;
9591
+ if (direction === "up") {
9592
+ return caretRect.top < containerRect.top + lineHeight;
9593
+ } else {
9594
+ return caretRect.bottom > containerRect.bottom - lineHeight;
9595
+ }
9596
+ }
9597
+ var TablePlugin = {
9598
+ name: "table",
9599
+ keyBindings: {
9600
+ Tab: (engine) => {
9601
+ var _a;
9602
+ const state = engine.getState();
9603
+ const sel = state.selection;
9604
+ if (!sel) return false;
9605
+ const cellPos = findCellPosition(state.doc, sel.anchor.path);
9606
+ if (!cellPos) return false;
9607
+ const { tablePath, row, col } = cellPos;
9608
+ const table = getNodeAtPath(state.doc, tablePath);
9609
+ const { rows, cols } = getTableDimensions(table);
9610
+ let nextRow = row;
9611
+ let nextCol = col + 1;
9612
+ while (nextRow < rows) {
9613
+ if (nextCol >= cols) {
9614
+ nextCol = 0;
9615
+ nextRow++;
9616
+ continue;
9617
+ }
9618
+ const c = getNodeAtPath(state.doc, [...tablePath, nextRow, nextCol]);
9619
+ if (!((_a = c == null ? void 0 : c.attrs) == null ? void 0 : _a.covered)) break;
9620
+ nextCol++;
9621
+ }
9622
+ if (nextRow >= rows) {
9623
+ const newTable = insertTableRowAfter(table, rows - 1);
9624
+ const newChildren = [...state.doc.children];
9625
+ newChildren[tablePath[0]] = newTable;
9626
+ const tempDoc = { type: "doc", children: newChildren };
9627
+ const tr2 = createTransaction();
9628
+ tr2.steps.push({ type: "delete_node", path: tablePath });
9629
+ tr2.steps.push({
9630
+ type: "insert_node",
9631
+ parentPath: tablePath.length > 1 ? tablePath.slice(0, -1) : [],
9632
+ index: tablePath[tablePath.length - 1],
9633
+ node: newTable
9634
+ });
9635
+ const pos2 = getCellFirstPosition(tempDoc, tablePath, rows, 0);
9636
+ if (pos2) tr2.steps.push(tr_setSelection(makeCollapsedSelection(pos2)));
9637
+ engine.dispatch(tr2);
9638
+ return true;
9639
+ }
9640
+ const pos = getCellFirstPosition(state.doc, tablePath, nextRow, nextCol);
9641
+ if (!pos) return false;
9642
+ const tr = createTransaction();
9643
+ tr.steps.push(tr_setSelection(makeCollapsedSelection(pos)));
9644
+ engine.dispatch(tr);
9645
+ return true;
9646
+ },
9647
+ "ArrowRight": (engine) => {
9648
+ var _a;
9649
+ const state = engine.getState();
9650
+ const sel = state.selection;
9651
+ if (!sel) return false;
9652
+ const cellPos = findCellPosition(state.doc, sel.anchor.path);
9653
+ if (!cellPos) return false;
9654
+ const { tablePath, row, col } = cellPos;
9655
+ const cellPath = [...tablePath, row, col];
9656
+ const cellEl = getCellDOMElement(JSON.stringify(cellPath));
9657
+ if (!cellEl || !isCaretAtContainerEnd(cellEl)) return false;
9658
+ const table = getNodeAtPath(state.doc, tablePath);
9659
+ const { rows, cols } = getTableDimensions(table);
9660
+ let nextRow = row;
9661
+ let nextCol = col + 1;
9662
+ while (nextRow < rows) {
9663
+ if (nextCol >= cols) {
9664
+ nextCol = 0;
9665
+ nextRow++;
9666
+ continue;
9667
+ }
9668
+ const c = getNodeAtPath(state.doc, [...tablePath, nextRow, nextCol]);
9669
+ if (!((_a = c == null ? void 0 : c.attrs) == null ? void 0 : _a.covered)) break;
9670
+ nextCol++;
9671
+ }
9672
+ if (nextRow >= rows) return true;
9673
+ const pos = getCellFirstPosition(state.doc, tablePath, nextRow, nextCol);
9674
+ if (!pos) return false;
9675
+ const tr = createTransaction();
9676
+ tr.steps.push(tr_setSelection(makeCollapsedSelection(pos)));
9677
+ engine.dispatch(tr);
9678
+ return true;
9679
+ },
9680
+ "ArrowLeft": (engine) => {
9681
+ var _a;
9682
+ const state = engine.getState();
9683
+ const sel = state.selection;
9684
+ if (!sel) return false;
9685
+ const cellPos = findCellPosition(state.doc, sel.anchor.path);
9686
+ if (!cellPos) return false;
9687
+ const { tablePath, row, col } = cellPos;
9688
+ const cellPath = [...tablePath, row, col];
9689
+ const cellEl = getCellDOMElement(JSON.stringify(cellPath));
9690
+ if (!cellEl || !isCaretAtContainerStart(cellEl)) return false;
9691
+ const table = getNodeAtPath(state.doc, tablePath);
9692
+ const { cols } = getTableDimensions(table);
9693
+ let prevRow = row;
9694
+ let prevCol = col - 1;
9695
+ while (prevRow >= 0) {
9696
+ if (prevCol < 0) {
9697
+ prevCol = cols - 1;
9698
+ prevRow--;
9699
+ continue;
9700
+ }
9701
+ const c = getNodeAtPath(state.doc, [...tablePath, prevRow, prevCol]);
9702
+ if (!((_a = c == null ? void 0 : c.attrs) == null ? void 0 : _a.covered)) break;
9703
+ prevCol--;
9704
+ }
9705
+ if (prevRow < 0) return true;
9706
+ const pos = getCellLastPosition(state.doc, tablePath, prevRow, prevCol);
9707
+ if (!pos) return false;
9708
+ const tr = createTransaction();
9709
+ tr.steps.push(tr_setSelection(makeCollapsedSelection(pos)));
8850
9710
  engine.dispatch(tr);
8851
9711
  return true;
8852
9712
  },
@@ -9148,151 +10008,6 @@ function execCommand(engine, command, ...args) {
9148
10008
  function registerCommand(name, fn) {
9149
10009
  REGISTRY[name] = fn;
9150
10010
  }
9151
- var Editor = react.forwardRef(function Editor2(props, ref) {
9152
- var _a;
9153
- const {
9154
- value,
9155
- defaultValue,
9156
- onChange,
9157
- placeholder,
9158
- readOnly = false,
9159
- toolbar = DEFAULT_TOOLBAR,
9160
- theme = "light",
9161
- plugins,
9162
- onFocus,
9163
- onBlur,
9164
- onReady,
9165
- onUploadImage,
9166
- className,
9167
- style,
9168
- minHeight,
9169
- maxHeight
9170
- } = props;
9171
- const engine = useEditorEngine();
9172
- const pluginsRegisteredRef = react.useRef(false);
9173
- if (!pluginsRegisteredRef.current && (plugins == null ? void 0 : plugins.length)) {
9174
- pluginsRegisteredRef.current = true;
9175
- for (const p of plugins) {
9176
- engine.registerPlugin(p);
9177
- }
9178
- }
9179
- const initializedRef = react.useRef(false);
9180
- react.useEffect(() => {
9181
- if (initializedRef.current) return;
9182
- initializedRef.current = true;
9183
- const initHTML = value != null ? value : defaultValue;
9184
- if (!initHTML) return;
9185
- const doc = htmlSerializer.deserialize(initHTML);
9186
- const tr = createTransaction();
9187
- tr.steps.push(tr_replaceDoc(doc));
9188
- engine.dispatch(tr);
9189
- }, []);
9190
- const lastEmittedRef = react.useRef("");
9191
- react.useEffect(() => {
9192
- if (value === void 0) return;
9193
- if (value === lastEmittedRef.current) return;
9194
- const currentHTML = htmlSerializer.serialize(engine.getState().doc);
9195
- if (value === currentHTML) return;
9196
- const doc = htmlSerializer.deserialize(value);
9197
- const tr = createTransaction();
9198
- tr.steps.push(tr_replaceDoc(doc));
9199
- engine.dispatch(tr);
9200
- }, [value, engine]);
9201
- const handleHTMLChange = react.useCallback((html) => {
9202
- lastEmittedRef.current = html;
9203
- onChange == null ? void 0 : onChange(html);
9204
- }, [onChange]);
9205
- const rootRef = react.useRef(null);
9206
- const api = react.useMemo(() => ({
9207
- getHTML: () => htmlSerializer.serialize(engine.getState().doc),
9208
- setHTML: (html) => {
9209
- const doc = htmlSerializer.deserialize(html);
9210
- const tr = createTransaction();
9211
- tr.steps.push(tr_replaceDoc(doc));
9212
- engine.dispatch(tr);
9213
- },
9214
- getJSON: () => engine.getState().doc,
9215
- getMarkdown: () => markdownSerializer.serialize(engine.getState().doc),
9216
- focus: () => {
9217
- var _a2;
9218
- const ce = (_a2 = rootRef.current) == null ? void 0 : _a2.querySelector("[contenteditable]");
9219
- ce == null ? void 0 : ce.focus();
9220
- },
9221
- blur: () => {
9222
- var _a2;
9223
- const ce = (_a2 = rootRef.current) == null ? void 0 : _a2.querySelector("[contenteditable]");
9224
- ce == null ? void 0 : ce.blur();
9225
- },
9226
- clear: () => {
9227
- const tr = createTransaction();
9228
- tr.steps.push(tr_replaceDoc(createEmptyDocument()));
9229
- engine.dispatch(tr);
9230
- },
9231
- undo: () => {
9232
- engine.handleKeyDown(
9233
- new KeyboardEvent("keydown", { key: "z", ctrlKey: true, bubbles: true })
9234
- );
9235
- },
9236
- redo: () => {
9237
- engine.handleKeyDown(
9238
- new KeyboardEvent("keydown", { key: "y", ctrlKey: true, bubbles: true })
9239
- );
9240
- },
9241
- execCommand: (command, ...args) => {
9242
- return execCommand(engine, command, ...args);
9243
- },
9244
- registerCommand: (name, fn) => {
9245
- registerCommand(name, fn);
9246
- },
9247
- getEngine: () => engine
9248
- }), [engine]);
9249
- react.useImperativeHandle(ref, () => api, [api]);
9250
- react.useEffect(() => {
9251
- onReady == null ? void 0 : onReady(api);
9252
- }, []);
9253
- const themeMode = typeof theme === "string" ? theme : (_a = theme.mode) != null ? _a : "light";
9254
- const themeTokenOverrides = typeof theme === "object" && theme.tokens ? theme.tokens : void 0;
9255
- const rootStyle = react.useMemo(() => {
9256
- const base = { ...style };
9257
- if (minHeight !== void 0)
9258
- base.minHeight = typeof minHeight === "number" ? `${minHeight}px` : minHeight;
9259
- if (maxHeight !== void 0) {
9260
- base.maxHeight = typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight;
9261
- base.overflow = "auto";
9262
- }
9263
- if (themeTokenOverrides) {
9264
- for (const [key, val] of Object.entries(themeTokenOverrides)) {
9265
- const cssVar = `--editor-${key.replace(/([A-Z])/g, (m) => `-${m.toLowerCase()}`)}`;
9266
- base[cssVar] = val;
9267
- }
9268
- }
9269
- return base;
9270
- }, [style, minHeight, maxHeight, themeTokenOverrides]);
9271
- return /* @__PURE__ */ jsxRuntime.jsx(
9272
- "div",
9273
- {
9274
- ref: rootRef,
9275
- "data-editor-theme": themeMode,
9276
- "data-traffica-editor": "",
9277
- className,
9278
- style: rootStyle,
9279
- children: /* @__PURE__ */ jsxRuntime.jsx(
9280
- EditorCore,
9281
- {
9282
- engine,
9283
- placeholder,
9284
- readOnly,
9285
- hideToolbar: toolbar === false,
9286
- toolbarConfig: toolbar !== false ? toolbar : void 0,
9287
- onHTMLChange: handleHTMLChange,
9288
- onFocus,
9289
- onBlur,
9290
- onUploadImage
9291
- }
9292
- )
9293
- }
9294
- );
9295
- });
9296
10011
 
9297
10012
  exports.BASIC_TOOLBAR = BASIC_TOOLBAR;
9298
10013
  exports.DEFAULT_TOOLBAR = DEFAULT_TOOLBAR;