@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.d.mts +15 -3
- package/dist/index.d.ts +15 -3
- package/dist/index.js +1038 -325
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1040 -327
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
-
|
|
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 &&
|
|
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
|
|
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
|
-
|
|
8683
|
-
|
|
8684
|
-
|
|
8685
|
-
|
|
8686
|
-
|
|
8687
|
-
|
|
8688
|
-
|
|
8689
|
-
|
|
8690
|
-
|
|
8691
|
-
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
|
|
8706
|
-
|
|
8707
|
-
|
|
8708
|
-
|
|
8709
|
-
|
|
8710
|
-
|
|
8711
|
-
|
|
8712
|
-
|
|
8713
|
-
|
|
8714
|
-
|
|
8715
|
-
|
|
8716
|
-
|
|
8717
|
-
|
|
8718
|
-
|
|
8719
|
-
|
|
8720
|
-
|
|
8721
|
-
|
|
8722
|
-
|
|
8723
|
-
|
|
8724
|
-
|
|
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
|
-
|
|
8806
|
-
|
|
8807
|
-
|
|
8808
|
-
|
|
8809
|
-
|
|
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
|
-
|
|
8839
|
-
|
|
8840
|
-
|
|
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(
|
|
8761
|
+
tr.steps.push(tr_replaceDoc(newDoc));
|
|
8843
8762
|
engine.dispatch(tr);
|
|
8844
|
-
|
|
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;
|