@trafica/editor 1.0.31 → 1.0.33

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