@trafica/editor 1.0.32 → 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 +865 -159
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +867 -161
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7833,6 +7833,857 @@ var jsonSerializer = {
|
|
|
7833
7833
|
}
|
|
7834
7834
|
}
|
|
7835
7835
|
};
|
|
7836
|
+
function Editor({
|
|
7837
|
+
engine,
|
|
7838
|
+
placeholder = "Start writing...",
|
|
7839
|
+
className = "",
|
|
7840
|
+
readOnly = false,
|
|
7841
|
+
onHTMLChange,
|
|
7842
|
+
onJSONChange,
|
|
7843
|
+
onOpenLinkPopup,
|
|
7844
|
+
onUploadImage: _onUploadImage
|
|
7845
|
+
}) {
|
|
7846
|
+
const containerRef = react.useRef(null);
|
|
7847
|
+
const isRenderingRef = react.useRef(false);
|
|
7848
|
+
const skipRestoreSelectionRef = react.useRef(false);
|
|
7849
|
+
const scrollCaretIntoView = react.useCallback(() => {
|
|
7850
|
+
var _a, _b;
|
|
7851
|
+
const sel = window.getSelection();
|
|
7852
|
+
if (!sel || sel.rangeCount === 0) return;
|
|
7853
|
+
const range = sel.getRangeAt(0).cloneRange();
|
|
7854
|
+
range.collapse(true);
|
|
7855
|
+
const caretRect = range.getBoundingClientRect();
|
|
7856
|
+
let rect = caretRect;
|
|
7857
|
+
if (!rect || rect.top === 0 && rect.bottom === 0 && rect.left === 0) {
|
|
7858
|
+
const node = range.startContainer;
|
|
7859
|
+
const el = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
|
|
7860
|
+
if (el) rect = el.getBoundingClientRect();
|
|
7861
|
+
}
|
|
7862
|
+
if (!rect || rect.bottom === 0) return;
|
|
7863
|
+
let scrollEl = (_b = (_a = containerRef.current) == null ? void 0 : _a.parentElement) != null ? _b : null;
|
|
7864
|
+
while (scrollEl) {
|
|
7865
|
+
const style = window.getComputedStyle(scrollEl);
|
|
7866
|
+
const overflow = style.overflow + style.overflowY;
|
|
7867
|
+
if (/auto|scroll/.test(overflow)) break;
|
|
7868
|
+
scrollEl = scrollEl.parentElement;
|
|
7869
|
+
}
|
|
7870
|
+
const PADDING = 24;
|
|
7871
|
+
if (scrollEl) {
|
|
7872
|
+
const containerRect = scrollEl.getBoundingClientRect();
|
|
7873
|
+
if (rect.bottom > containerRect.bottom - PADDING) {
|
|
7874
|
+
scrollEl.scrollBy({ top: rect.bottom - containerRect.bottom + PADDING, behavior: "smooth" });
|
|
7875
|
+
} else if (rect.top < containerRect.top + PADDING) {
|
|
7876
|
+
scrollEl.scrollBy({ top: rect.top - containerRect.top - PADDING, behavior: "smooth" });
|
|
7877
|
+
}
|
|
7878
|
+
} else {
|
|
7879
|
+
if (rect.bottom > window.innerHeight - PADDING) {
|
|
7880
|
+
window.scrollBy({ top: rect.bottom - window.innerHeight + PADDING, behavior: "smooth" });
|
|
7881
|
+
} else if (rect.top < PADDING) {
|
|
7882
|
+
window.scrollBy({ top: rect.top - PADDING, behavior: "smooth" });
|
|
7883
|
+
}
|
|
7884
|
+
}
|
|
7885
|
+
}, []);
|
|
7886
|
+
const isComposingRef = react.useRef(false);
|
|
7887
|
+
const stateRef = react.useRef(engine.getState());
|
|
7888
|
+
const [selectedImagePath, setSelectedImagePath] = react.useState(null);
|
|
7889
|
+
const [findReplaceOpen, setFindReplaceOpen] = react.useState(false);
|
|
7890
|
+
const [findReplaceMode, setFindReplaceMode] = react.useState("find");
|
|
7891
|
+
const [linkPopupOpen, setLinkPopupOpen] = react.useState(false);
|
|
7892
|
+
const [isSourceMode, setIsSourceMode] = react.useState(false);
|
|
7893
|
+
const [sourceHTML, setSourceHTML] = react.useState("");
|
|
7894
|
+
const [editorHeight, setEditorHeight] = react.useState(null);
|
|
7895
|
+
const editorRootRef = react.useRef(null);
|
|
7896
|
+
const resizeDragRef = react.useRef(null);
|
|
7897
|
+
const [linkTooltip, setLinkTooltip] = react.useState(null);
|
|
7898
|
+
const linkTooltipTimerRef = react.useRef(null);
|
|
7899
|
+
const handleToggleSource = react.useCallback(() => {
|
|
7900
|
+
if (!isSourceMode) {
|
|
7901
|
+
const html = htmlSerializer.serialize(engine.getState().doc);
|
|
7902
|
+
setSourceHTML(prettyPrintHTML(html));
|
|
7903
|
+
setIsSourceMode(true);
|
|
7904
|
+
} else {
|
|
7905
|
+
const newDoc = htmlSerializer.deserialize(sourceHTML);
|
|
7906
|
+
const tr = createTransaction();
|
|
7907
|
+
tr.steps.push(tr_replaceDoc(newDoc));
|
|
7908
|
+
engine.dispatch(tr);
|
|
7909
|
+
setIsSourceMode(false);
|
|
7910
|
+
}
|
|
7911
|
+
}, [isSourceMode, sourceHTML, engine]);
|
|
7912
|
+
react.useEffect(() => {
|
|
7913
|
+
const onMouseMove = (e) => {
|
|
7914
|
+
const drag = resizeDragRef.current;
|
|
7915
|
+
if (!drag) return;
|
|
7916
|
+
const newHeight = Math.max(120, drag.startHeight + (e.clientY - drag.startY));
|
|
7917
|
+
setEditorHeight(newHeight);
|
|
7918
|
+
};
|
|
7919
|
+
const onMouseUp = () => {
|
|
7920
|
+
resizeDragRef.current = null;
|
|
7921
|
+
};
|
|
7922
|
+
document.addEventListener("mousemove", onMouseMove);
|
|
7923
|
+
document.addEventListener("mouseup", onMouseUp);
|
|
7924
|
+
return () => {
|
|
7925
|
+
document.removeEventListener("mousemove", onMouseMove);
|
|
7926
|
+
document.removeEventListener("mouseup", onMouseUp);
|
|
7927
|
+
};
|
|
7928
|
+
}, []);
|
|
7929
|
+
const [tableContextMenu, setTableContextMenu] = react.useState(null);
|
|
7930
|
+
const [tableSelection, setTableSelection] = react.useState(null);
|
|
7931
|
+
const cellDragRef = react.useRef(null);
|
|
7932
|
+
const colResizeRef = react.useRef(null);
|
|
7933
|
+
const state = useEditorState(engine);
|
|
7934
|
+
const inTableCellPos = state.selection ? findCellPosition(state.doc, state.selection.anchor.path) : null;
|
|
7935
|
+
react.useLayoutEffect(() => {
|
|
7936
|
+
stateRef.current = state;
|
|
7937
|
+
}, [state]);
|
|
7938
|
+
react.useLayoutEffect(() => {
|
|
7939
|
+
const container = containerRef.current;
|
|
7940
|
+
if (!container) return;
|
|
7941
|
+
isRenderingRef.current = true;
|
|
7942
|
+
renderDocument(state.doc, container);
|
|
7943
|
+
if (!container.contains(document.activeElement)) {
|
|
7944
|
+
container.focus({ preventScroll: true });
|
|
7945
|
+
}
|
|
7946
|
+
if (state.selection) {
|
|
7947
|
+
restoreSelection(container, state.selection);
|
|
7948
|
+
scrollCaretIntoView();
|
|
7949
|
+
}
|
|
7950
|
+
isRenderingRef.current = false;
|
|
7951
|
+
if (onHTMLChange) {
|
|
7952
|
+
onHTMLChange(htmlSerializer.serialize(state.doc));
|
|
7953
|
+
}
|
|
7954
|
+
if (onJSONChange) {
|
|
7955
|
+
onJSONChange(jsonSerializer.serialize(state.doc));
|
|
7956
|
+
}
|
|
7957
|
+
}, [state.doc]);
|
|
7958
|
+
react.useLayoutEffect(() => {
|
|
7959
|
+
const container = containerRef.current;
|
|
7960
|
+
if (!container || !state.selection) return;
|
|
7961
|
+
if (skipRestoreSelectionRef.current) {
|
|
7962
|
+
skipRestoreSelectionRef.current = false;
|
|
7963
|
+
return;
|
|
7964
|
+
}
|
|
7965
|
+
isRenderingRef.current = true;
|
|
7966
|
+
restoreSelection(container, state.selection);
|
|
7967
|
+
scrollCaretIntoView();
|
|
7968
|
+
isRenderingRef.current = false;
|
|
7969
|
+
}, [state.selection]);
|
|
7970
|
+
react.useEffect(() => {
|
|
7971
|
+
const container = containerRef.current;
|
|
7972
|
+
if (!container || readOnly) return;
|
|
7973
|
+
return attachClipboardHandlers(container, engine);
|
|
7974
|
+
}, [engine, readOnly]);
|
|
7975
|
+
react.useEffect(() => {
|
|
7976
|
+
const container = containerRef.current;
|
|
7977
|
+
if (!container) return;
|
|
7978
|
+
container.querySelectorAll(".editor-cell-selected").forEach((el) => {
|
|
7979
|
+
el.classList.remove("editor-cell-selected");
|
|
7980
|
+
});
|
|
7981
|
+
if (!tableSelection) return;
|
|
7982
|
+
const { tablePath, anchorCell, focusCell } = tableSelection;
|
|
7983
|
+
const minRow = Math.min(anchorCell[0], focusCell[0]);
|
|
7984
|
+
const maxRow = Math.max(anchorCell[0], focusCell[0]);
|
|
7985
|
+
const minCol = Math.min(anchorCell[1], focusCell[1]);
|
|
7986
|
+
const maxCol = Math.max(anchorCell[1], focusCell[1]);
|
|
7987
|
+
for (let r = minRow; r <= maxRow; r++) {
|
|
7988
|
+
for (let c = minCol; c <= maxCol; c++) {
|
|
7989
|
+
const cellPath = [...tablePath, r, c];
|
|
7990
|
+
const td = container.querySelector(
|
|
7991
|
+
`[data-block-path="${JSON.stringify(cellPath)}"]`
|
|
7992
|
+
);
|
|
7993
|
+
if (td) td.classList.add("editor-cell-selected");
|
|
7994
|
+
}
|
|
7995
|
+
}
|
|
7996
|
+
}, [tableSelection, state.doc]);
|
|
7997
|
+
react.useEffect(() => {
|
|
7998
|
+
const container = containerRef.current;
|
|
7999
|
+
if (!container) return;
|
|
8000
|
+
container.querySelectorAll(".editor-cell-active").forEach((el) => {
|
|
8001
|
+
el.classList.remove("editor-cell-active");
|
|
8002
|
+
});
|
|
8003
|
+
if (!inTableCellPos) {
|
|
8004
|
+
setTableSelection(null);
|
|
8005
|
+
return;
|
|
8006
|
+
}
|
|
8007
|
+
if (tableSelection) {
|
|
8008
|
+
const { anchorCell, focusCell, tablePath } = tableSelection;
|
|
8009
|
+
const sameTable = JSON.stringify(tablePath) === JSON.stringify(inTableCellPos.tablePath);
|
|
8010
|
+
const singleCell = anchorCell[0] === focusCell[0] && anchorCell[1] === focusCell[1];
|
|
8011
|
+
if (!sameTable || singleCell) {
|
|
8012
|
+
setTableSelection(null);
|
|
8013
|
+
}
|
|
8014
|
+
}
|
|
8015
|
+
const cellPath = [...inTableCellPos.tablePath, inTableCellPos.row, inTableCellPos.col];
|
|
8016
|
+
const td = container.querySelector(
|
|
8017
|
+
`[data-block-path="${JSON.stringify(cellPath)}"]`
|
|
8018
|
+
);
|
|
8019
|
+
if (td) td.classList.add("editor-cell-active");
|
|
8020
|
+
}, [inTableCellPos, state.doc]);
|
|
8021
|
+
react.useEffect(() => {
|
|
8022
|
+
const onSelectionChange = () => {
|
|
8023
|
+
if (isRenderingRef.current) return;
|
|
8024
|
+
const container = containerRef.current;
|
|
8025
|
+
if (!container) return;
|
|
8026
|
+
if (!isSelectionInContainer(container)) return;
|
|
8027
|
+
const captured = captureSelection(container);
|
|
8028
|
+
if (!captured) return;
|
|
8029
|
+
const currentSelection = engine.getState().selection;
|
|
8030
|
+
if (currentSelection && JSON.stringify(currentSelection.anchor) === JSON.stringify(captured.anchor) && JSON.stringify(currentSelection.focus) === JSON.stringify(captured.focus)) {
|
|
8031
|
+
return;
|
|
8032
|
+
}
|
|
8033
|
+
skipRestoreSelectionRef.current = true;
|
|
8034
|
+
const tr = createTransaction();
|
|
8035
|
+
tr.steps.push(tr_setSelection(captured));
|
|
8036
|
+
engine.dispatch(tr);
|
|
8037
|
+
};
|
|
8038
|
+
document.addEventListener(
|
|
8039
|
+
"selectionchange",
|
|
8040
|
+
onSelectionChange
|
|
8041
|
+
);
|
|
8042
|
+
return () => {
|
|
8043
|
+
document.removeEventListener(
|
|
8044
|
+
"selectionchange",
|
|
8045
|
+
onSelectionChange
|
|
8046
|
+
);
|
|
8047
|
+
};
|
|
8048
|
+
}, [engine]);
|
|
8049
|
+
const handleFocus = react.useCallback(() => {
|
|
8050
|
+
const currentState = engine.getState();
|
|
8051
|
+
if (!currentState.selection) {
|
|
8052
|
+
const tr = createTransaction();
|
|
8053
|
+
tr.steps.push(
|
|
8054
|
+
tr_setSelection(
|
|
8055
|
+
makeCollapsedSelection({ path: [0], offset: 0 })
|
|
8056
|
+
)
|
|
8057
|
+
);
|
|
8058
|
+
engine.dispatch(tr);
|
|
8059
|
+
}
|
|
8060
|
+
}, [engine]);
|
|
8061
|
+
const handleKeyDown = react.useCallback(
|
|
8062
|
+
(e) => {
|
|
8063
|
+
var _a, _b, _c;
|
|
8064
|
+
if (readOnly) return;
|
|
8065
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "f") {
|
|
8066
|
+
e.preventDefault();
|
|
8067
|
+
setFindReplaceMode("find");
|
|
8068
|
+
setFindReplaceOpen(true);
|
|
8069
|
+
return;
|
|
8070
|
+
}
|
|
8071
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "h") {
|
|
8072
|
+
e.preventDefault();
|
|
8073
|
+
setFindReplaceMode("replace");
|
|
8074
|
+
setFindReplaceOpen(true);
|
|
8075
|
+
return;
|
|
8076
|
+
}
|
|
8077
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "k") {
|
|
8078
|
+
e.preventDefault();
|
|
8079
|
+
setLinkPopupOpen(true);
|
|
8080
|
+
onOpenLinkPopup == null ? void 0 : onOpenLinkPopup();
|
|
8081
|
+
return;
|
|
8082
|
+
}
|
|
8083
|
+
if ((e.ctrlKey || e.metaKey) && (e.key === "a" || e.key === "A")) {
|
|
8084
|
+
e.preventDefault();
|
|
8085
|
+
const container = containerRef.current;
|
|
8086
|
+
if (container) {
|
|
8087
|
+
const spans = container.querySelectorAll("[data-path]");
|
|
8088
|
+
const first = spans[0];
|
|
8089
|
+
const last = spans[spans.length - 1];
|
|
8090
|
+
if (first && last) {
|
|
8091
|
+
const sel = window.getSelection();
|
|
8092
|
+
const range = document.createRange();
|
|
8093
|
+
range.setStart((_a = leafTextNode(first, "first")) != null ? _a : first, 0);
|
|
8094
|
+
const lastLeaf = leafTextNode(last, "last");
|
|
8095
|
+
range.setEnd(lastLeaf != null ? lastLeaf : last, (_c = (_b = lastLeaf == null ? void 0 : lastLeaf.textContent) == null ? void 0 : _b.length) != null ? _c : 0);
|
|
8096
|
+
sel == null ? void 0 : sel.removeAllRanges();
|
|
8097
|
+
sel == null ? void 0 : sel.addRange(range);
|
|
8098
|
+
}
|
|
8099
|
+
}
|
|
8100
|
+
return;
|
|
8101
|
+
}
|
|
8102
|
+
if (selectedImagePath && (e.key === "Delete" || e.key === "Backspace")) {
|
|
8103
|
+
e.preventDefault();
|
|
8104
|
+
deleteImageAtPath(selectedImagePath)(engine);
|
|
8105
|
+
setSelectedImagePath(null);
|
|
8106
|
+
return;
|
|
8107
|
+
}
|
|
8108
|
+
if (selectedImagePath) setSelectedImagePath(null);
|
|
8109
|
+
if (e.key === "Tab") {
|
|
8110
|
+
const { doc, selection: sel } = engine.getState();
|
|
8111
|
+
if (sel) {
|
|
8112
|
+
const bp = findContentBlockPath(doc, sel.anchor.path);
|
|
8113
|
+
const block = bp ? getNodeAtPath(doc, bp) : null;
|
|
8114
|
+
if ((block == null ? void 0 : block.type) === "code_block") {
|
|
8115
|
+
e.preventDefault();
|
|
8116
|
+
insertText(" ")(engine);
|
|
8117
|
+
return;
|
|
8118
|
+
}
|
|
8119
|
+
if ((block == null ? void 0 : block.type) === "list_item") {
|
|
8120
|
+
e.preventDefault();
|
|
8121
|
+
if (e.shiftKey) {
|
|
8122
|
+
outdentListItem(engine);
|
|
8123
|
+
} else {
|
|
8124
|
+
indentListItem(engine);
|
|
8125
|
+
}
|
|
8126
|
+
return;
|
|
8127
|
+
}
|
|
8128
|
+
if (findCellPosition(doc, sel.anchor.path)) {
|
|
8129
|
+
e.preventDefault();
|
|
8130
|
+
}
|
|
8131
|
+
}
|
|
8132
|
+
}
|
|
8133
|
+
const handled = engine.handleKeyDown(
|
|
8134
|
+
e.nativeEvent
|
|
8135
|
+
);
|
|
8136
|
+
if (handled) return;
|
|
8137
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
8138
|
+
e.preventDefault();
|
|
8139
|
+
handleEnter(engine);
|
|
8140
|
+
return;
|
|
8141
|
+
}
|
|
8142
|
+
if (e.key === "Backspace") {
|
|
8143
|
+
e.preventDefault();
|
|
8144
|
+
handleBackspace(engine);
|
|
8145
|
+
return;
|
|
8146
|
+
}
|
|
8147
|
+
if (e.key === "Delete") {
|
|
8148
|
+
e.preventDefault();
|
|
8149
|
+
handleDelete(engine);
|
|
8150
|
+
return;
|
|
8151
|
+
}
|
|
8152
|
+
if (e.key === "Enter" && e.shiftKey) {
|
|
8153
|
+
e.preventDefault();
|
|
8154
|
+
insertText("\n")(engine);
|
|
8155
|
+
return;
|
|
8156
|
+
}
|
|
8157
|
+
},
|
|
8158
|
+
[engine, readOnly, onOpenLinkPopup, selectedImagePath]
|
|
8159
|
+
);
|
|
8160
|
+
react.useEffect(() => {
|
|
8161
|
+
const container = containerRef.current;
|
|
8162
|
+
if (!container || readOnly) return;
|
|
8163
|
+
const onBeforeInput = (e) => {
|
|
8164
|
+
if (e.isComposing || isComposingRef.current) return;
|
|
8165
|
+
switch (e.inputType) {
|
|
8166
|
+
case "insertText":
|
|
8167
|
+
case "insertReplacementText": {
|
|
8168
|
+
if (!e.data) return;
|
|
8169
|
+
e.preventDefault();
|
|
8170
|
+
insertText(e.data)(engine);
|
|
8171
|
+
return;
|
|
8172
|
+
}
|
|
8173
|
+
case "insertParagraph": {
|
|
8174
|
+
e.preventDefault();
|
|
8175
|
+
handleEnter(engine);
|
|
8176
|
+
return;
|
|
8177
|
+
}
|
|
8178
|
+
case "insertLineBreak": {
|
|
8179
|
+
e.preventDefault();
|
|
8180
|
+
insertText("\n")(engine);
|
|
8181
|
+
return;
|
|
8182
|
+
}
|
|
8183
|
+
case "deleteContentBackward":
|
|
8184
|
+
case "deleteWordBackward":
|
|
8185
|
+
case "deleteSoftLineBackward": {
|
|
8186
|
+
e.preventDefault();
|
|
8187
|
+
handleBackspace(engine);
|
|
8188
|
+
return;
|
|
8189
|
+
}
|
|
8190
|
+
case "insertFromPaste":
|
|
8191
|
+
case "insertFromDrop":
|
|
8192
|
+
return;
|
|
8193
|
+
default:
|
|
8194
|
+
e.preventDefault();
|
|
8195
|
+
}
|
|
8196
|
+
};
|
|
8197
|
+
container.addEventListener("beforeinput", onBeforeInput);
|
|
8198
|
+
return () => container.removeEventListener("beforeinput", onBeforeInput);
|
|
8199
|
+
}, [engine, readOnly]);
|
|
8200
|
+
react.useEffect(() => {
|
|
8201
|
+
const container = containerRef.current;
|
|
8202
|
+
if (!container || readOnly) return;
|
|
8203
|
+
const onCompositionStart = () => {
|
|
8204
|
+
isComposingRef.current = true;
|
|
8205
|
+
};
|
|
8206
|
+
const onCompositionEnd = (e) => {
|
|
8207
|
+
isComposingRef.current = false;
|
|
8208
|
+
if (e.data) insertText(e.data)(engine);
|
|
8209
|
+
};
|
|
8210
|
+
container.addEventListener("compositionstart", onCompositionStart);
|
|
8211
|
+
container.addEventListener("compositionend", onCompositionEnd);
|
|
8212
|
+
return () => {
|
|
8213
|
+
container.removeEventListener("compositionstart", onCompositionStart);
|
|
8214
|
+
container.removeEventListener("compositionend", onCompositionEnd);
|
|
8215
|
+
};
|
|
8216
|
+
}, [engine, readOnly]);
|
|
8217
|
+
react.useEffect(() => {
|
|
8218
|
+
const container = containerRef.current;
|
|
8219
|
+
if (!container) return;
|
|
8220
|
+
const onMouseOver = (e) => {
|
|
8221
|
+
var _a;
|
|
8222
|
+
const anchor = e.target.closest("a.editor-link");
|
|
8223
|
+
if (!anchor) return;
|
|
8224
|
+
const href = (_a = anchor.getAttribute("href")) != null ? _a : "";
|
|
8225
|
+
if (!href) return;
|
|
8226
|
+
if (linkTooltipTimerRef.current) clearTimeout(linkTooltipTimerRef.current);
|
|
8227
|
+
linkTooltipTimerRef.current = setTimeout(() => {
|
|
8228
|
+
const rect = anchor.getBoundingClientRect();
|
|
8229
|
+
const containerRect = container.getBoundingClientRect();
|
|
8230
|
+
setLinkTooltip({
|
|
8231
|
+
href,
|
|
8232
|
+
x: rect.left - containerRect.left,
|
|
8233
|
+
y: rect.bottom - containerRect.top + 6
|
|
8234
|
+
});
|
|
8235
|
+
}, 200);
|
|
8236
|
+
};
|
|
8237
|
+
const onMouseOut = (e) => {
|
|
8238
|
+
const anchor = e.target.closest("a.editor-link");
|
|
8239
|
+
if (!anchor) return;
|
|
8240
|
+
if (linkTooltipTimerRef.current) clearTimeout(linkTooltipTimerRef.current);
|
|
8241
|
+
setLinkTooltip(null);
|
|
8242
|
+
};
|
|
8243
|
+
const onClick = (e) => {
|
|
8244
|
+
if (!(e.ctrlKey || e.metaKey)) return;
|
|
8245
|
+
const anchor = e.target.closest("a.editor-link");
|
|
8246
|
+
if (!anchor) return;
|
|
8247
|
+
e.preventDefault();
|
|
8248
|
+
const href = anchor.getAttribute("href");
|
|
8249
|
+
if (href) window.open(href, "_blank", "noopener,noreferrer");
|
|
8250
|
+
};
|
|
8251
|
+
container.addEventListener("mouseover", onMouseOver);
|
|
8252
|
+
container.addEventListener("mouseout", onMouseOut);
|
|
8253
|
+
container.addEventListener("click", onClick);
|
|
8254
|
+
return () => {
|
|
8255
|
+
container.removeEventListener("mouseover", onMouseOver);
|
|
8256
|
+
container.removeEventListener("mouseout", onMouseOut);
|
|
8257
|
+
container.removeEventListener("click", onClick);
|
|
8258
|
+
if (linkTooltipTimerRef.current) clearTimeout(linkTooltipTimerRef.current);
|
|
8259
|
+
};
|
|
8260
|
+
}, []);
|
|
8261
|
+
react.useEffect(() => {
|
|
8262
|
+
const onMouseMove = (e) => {
|
|
8263
|
+
const drag = colResizeRef.current;
|
|
8264
|
+
if (!drag) return;
|
|
8265
|
+
const delta = e.clientX - drag.startX;
|
|
8266
|
+
const newWidth = Math.max(40, drag.startWidth + delta);
|
|
8267
|
+
const container = containerRef.current;
|
|
8268
|
+
if (container) {
|
|
8269
|
+
const colEl = container.querySelector(
|
|
8270
|
+
`col[data-col-index="${drag.colIndex}"]`
|
|
8271
|
+
);
|
|
8272
|
+
if (colEl) colEl.style.width = `${newWidth}px`;
|
|
8273
|
+
}
|
|
8274
|
+
};
|
|
8275
|
+
const onMouseUp = (e) => {
|
|
8276
|
+
const drag = colResizeRef.current;
|
|
8277
|
+
if (!drag) return;
|
|
8278
|
+
const delta = e.clientX - drag.startX;
|
|
8279
|
+
const newWidth = Math.max(40, drag.startWidth + delta);
|
|
8280
|
+
colResizeRef.current = null;
|
|
8281
|
+
setColumnWidth(drag.tablePath, drag.colIndex, newWidth)(engine);
|
|
8282
|
+
};
|
|
8283
|
+
document.addEventListener("mousemove", onMouseMove);
|
|
8284
|
+
document.addEventListener("mouseup", onMouseUp);
|
|
8285
|
+
return () => {
|
|
8286
|
+
document.removeEventListener("mousemove", onMouseMove);
|
|
8287
|
+
document.removeEventListener("mouseup", onMouseUp);
|
|
8288
|
+
};
|
|
8289
|
+
}, [engine]);
|
|
8290
|
+
react.useEffect(() => {
|
|
8291
|
+
const onMouseMove = (e) => {
|
|
8292
|
+
var _a, _b;
|
|
8293
|
+
const drag = cellDragRef.current;
|
|
8294
|
+
if (!drag) return;
|
|
8295
|
+
const td = e.target.closest("[data-cell-row]");
|
|
8296
|
+
if (!td) return;
|
|
8297
|
+
const row = parseInt((_a = td.dataset.cellRow) != null ? _a : "0");
|
|
8298
|
+
const col = parseInt((_b = td.dataset.cellCol) != null ? _b : "0");
|
|
8299
|
+
const tdTablePath = td.dataset.cellTablePath ? JSON.parse(td.dataset.cellTablePath) : null;
|
|
8300
|
+
if (!tdTablePath || JSON.stringify(tdTablePath) !== JSON.stringify(drag.tablePath)) return;
|
|
8301
|
+
setTableSelection({
|
|
8302
|
+
tablePath: drag.tablePath,
|
|
8303
|
+
anchorCell: [drag.startRow, drag.startCol],
|
|
8304
|
+
focusCell: [row, col]
|
|
8305
|
+
});
|
|
8306
|
+
};
|
|
8307
|
+
const onMouseUp = () => {
|
|
8308
|
+
cellDragRef.current = null;
|
|
8309
|
+
};
|
|
8310
|
+
document.addEventListener("mousemove", onMouseMove);
|
|
8311
|
+
document.addEventListener("mouseup", onMouseUp);
|
|
8312
|
+
return () => {
|
|
8313
|
+
document.removeEventListener("mousemove", onMouseMove);
|
|
8314
|
+
document.removeEventListener("mouseup", onMouseUp);
|
|
8315
|
+
};
|
|
8316
|
+
}, []);
|
|
8317
|
+
const handleContextMenu = react.useCallback(
|
|
8318
|
+
(e) => {
|
|
8319
|
+
var _a, _b, _c, _d, _e, _f;
|
|
8320
|
+
const td = e.target.closest("[data-cell-row]");
|
|
8321
|
+
if (!td) return;
|
|
8322
|
+
e.preventDefault();
|
|
8323
|
+
const row = parseInt((_a = td.dataset.cellRow) != null ? _a : "0");
|
|
8324
|
+
const col = parseInt((_b = td.dataset.cellCol) != null ? _b : "0");
|
|
8325
|
+
const tablePath = td.dataset.cellTablePath ? JSON.parse(td.dataset.cellTablePath) : null;
|
|
8326
|
+
if (!tablePath) return;
|
|
8327
|
+
const cellNode = getNodeAtPath(
|
|
8328
|
+
engine.getState().doc,
|
|
8329
|
+
[...tablePath, row, col]
|
|
8330
|
+
);
|
|
8331
|
+
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);
|
|
8332
|
+
setTableContextMenu({ x: e.clientX, y: e.clientY, tablePath, row, col, isMerged });
|
|
8333
|
+
},
|
|
8334
|
+
[engine]
|
|
8335
|
+
);
|
|
8336
|
+
const handleMouseDown = react.useCallback(
|
|
8337
|
+
(e) => {
|
|
8338
|
+
var _a, _b, _c, _d;
|
|
8339
|
+
if (readOnly) return;
|
|
8340
|
+
const target = e.target;
|
|
8341
|
+
if (target.dataset.resizeImagePath) {
|
|
8342
|
+
e.preventDefault();
|
|
8343
|
+
const imagePath = JSON.parse(target.dataset.resizeImagePath);
|
|
8344
|
+
const corner = (_a = target.dataset.resizeImagePos) != null ? _a : "se";
|
|
8345
|
+
const container = containerRef.current;
|
|
8346
|
+
let startWidth = 300;
|
|
8347
|
+
if (container) {
|
|
8348
|
+
const fig2 = container.querySelector(`[data-image-path="${JSON.stringify(imagePath)}"]`);
|
|
8349
|
+
const img = fig2 == null ? void 0 : fig2.querySelector("img");
|
|
8350
|
+
if (img) startWidth = img.getBoundingClientRect().width || 300;
|
|
8351
|
+
}
|
|
8352
|
+
imageResizeRef.current = {
|
|
8353
|
+
imagePath,
|
|
8354
|
+
corner,
|
|
8355
|
+
startX: e.clientX,
|
|
8356
|
+
startY: e.clientY,
|
|
8357
|
+
startWidth,
|
|
8358
|
+
startHeight: 0
|
|
8359
|
+
};
|
|
8360
|
+
return;
|
|
8361
|
+
}
|
|
8362
|
+
const fig = target.closest("[data-image-path]");
|
|
8363
|
+
if (fig == null ? void 0 : fig.dataset.imagePath) {
|
|
8364
|
+
e.preventDefault();
|
|
8365
|
+
setSelectedImagePath(JSON.parse(fig.dataset.imagePath));
|
|
8366
|
+
return;
|
|
8367
|
+
}
|
|
8368
|
+
setSelectedImagePath(null);
|
|
8369
|
+
if (target.dataset.resizeTable) {
|
|
8370
|
+
e.preventDefault();
|
|
8371
|
+
const tablePath = JSON.parse(target.dataset.resizeTable);
|
|
8372
|
+
const colIndex = parseInt((_b = target.dataset.resizeCol) != null ? _b : "0");
|
|
8373
|
+
const container = containerRef.current;
|
|
8374
|
+
let startWidth = 120;
|
|
8375
|
+
if (container) {
|
|
8376
|
+
const colEl = container.querySelector(
|
|
8377
|
+
`col[data-col-index="${colIndex}"]`
|
|
8378
|
+
);
|
|
8379
|
+
if (colEl) startWidth = colEl.getBoundingClientRect().width || 120;
|
|
8380
|
+
}
|
|
8381
|
+
colResizeRef.current = { tablePath, colIndex, startX: e.clientX, startWidth };
|
|
8382
|
+
return;
|
|
8383
|
+
}
|
|
8384
|
+
const td = target.closest("[data-cell-row]");
|
|
8385
|
+
if (!td) setTableSelection(null);
|
|
8386
|
+
if (td && !readOnly) {
|
|
8387
|
+
const row = parseInt((_c = td.dataset.cellRow) != null ? _c : "0");
|
|
8388
|
+
const col = parseInt((_d = td.dataset.cellCol) != null ? _d : "0");
|
|
8389
|
+
const tdTablePath = td.dataset.cellTablePath ? JSON.parse(td.dataset.cellTablePath) : null;
|
|
8390
|
+
if (tdTablePath) {
|
|
8391
|
+
cellDragRef.current = { tablePath: tdTablePath, startRow: row, startCol: col };
|
|
8392
|
+
setTableSelection({
|
|
8393
|
+
tablePath: tdTablePath,
|
|
8394
|
+
anchorCell: [row, col],
|
|
8395
|
+
focusCell: [row, col]
|
|
8396
|
+
});
|
|
8397
|
+
}
|
|
8398
|
+
}
|
|
8399
|
+
const checkTarget = target.dataset.checkPath ? target : target.closest("[data-check-path]");
|
|
8400
|
+
if (checkTarget == null ? void 0 : checkTarget.dataset.checkPath) {
|
|
8401
|
+
e.preventDefault();
|
|
8402
|
+
toggleCheckItemAt(JSON.parse(checkTarget.dataset.checkPath))(engine);
|
|
8403
|
+
}
|
|
8404
|
+
},
|
|
8405
|
+
[engine, readOnly]
|
|
8406
|
+
);
|
|
8407
|
+
const handleClick = react.useCallback(() => {
|
|
8408
|
+
const container = containerRef.current;
|
|
8409
|
+
if (!container) return;
|
|
8410
|
+
const selection = captureSelection(container);
|
|
8411
|
+
if (!selection) return;
|
|
8412
|
+
skipRestoreSelectionRef.current = true;
|
|
8413
|
+
const tr = createTransaction();
|
|
8414
|
+
tr.steps.push(
|
|
8415
|
+
tr_setSelection(selection)
|
|
8416
|
+
);
|
|
8417
|
+
engine.dispatch(tr);
|
|
8418
|
+
}, [engine]);
|
|
8419
|
+
const imageResizeRef = react.useRef(null);
|
|
8420
|
+
react.useEffect(() => {
|
|
8421
|
+
const onMouseMove = (e) => {
|
|
8422
|
+
const drag = imageResizeRef.current;
|
|
8423
|
+
if (!drag) return;
|
|
8424
|
+
const deltaX = e.clientX - drag.startX;
|
|
8425
|
+
const isRight = drag.corner === "ne" || drag.corner === "se";
|
|
8426
|
+
const newWidth = Math.max(60, drag.startWidth + (isRight ? deltaX : -deltaX));
|
|
8427
|
+
const container = containerRef.current;
|
|
8428
|
+
if (container) {
|
|
8429
|
+
const fig = container.querySelector(
|
|
8430
|
+
`[data-image-path="${JSON.stringify(drag.imagePath)}"]`
|
|
8431
|
+
);
|
|
8432
|
+
const img = fig == null ? void 0 : fig.querySelector("img");
|
|
8433
|
+
if (img) img.style.width = `${newWidth}px`;
|
|
8434
|
+
}
|
|
8435
|
+
};
|
|
8436
|
+
const onMouseUp = (e) => {
|
|
8437
|
+
const drag = imageResizeRef.current;
|
|
8438
|
+
if (!drag) return;
|
|
8439
|
+
const deltaX = e.clientX - drag.startX;
|
|
8440
|
+
const isRight = drag.corner === "ne" || drag.corner === "se";
|
|
8441
|
+
const newWidth = Math.max(60, drag.startWidth + (isRight ? deltaX : -deltaX));
|
|
8442
|
+
imageResizeRef.current = null;
|
|
8443
|
+
setImageAttr(drag.imagePath, { width: newWidth })(engine);
|
|
8444
|
+
};
|
|
8445
|
+
document.addEventListener("mousemove", onMouseMove);
|
|
8446
|
+
document.addEventListener("mouseup", onMouseUp);
|
|
8447
|
+
return () => {
|
|
8448
|
+
document.removeEventListener("mousemove", onMouseMove);
|
|
8449
|
+
document.removeEventListener("mouseup", onMouseUp);
|
|
8450
|
+
};
|
|
8451
|
+
}, [engine]);
|
|
8452
|
+
react.useEffect(() => {
|
|
8453
|
+
const container = containerRef.current;
|
|
8454
|
+
if (!container) return;
|
|
8455
|
+
container.querySelectorAll("[data-image-path]").forEach((el) => {
|
|
8456
|
+
el.removeAttribute("data-selected");
|
|
8457
|
+
});
|
|
8458
|
+
if (selectedImagePath) {
|
|
8459
|
+
const fig = container.querySelector(
|
|
8460
|
+
`[data-image-path="${JSON.stringify(selectedImagePath)}"]`
|
|
8461
|
+
);
|
|
8462
|
+
if (fig) fig.setAttribute("data-selected", "true");
|
|
8463
|
+
}
|
|
8464
|
+
}, [selectedImagePath, state.doc]);
|
|
8465
|
+
react.useEffect(() => {
|
|
8466
|
+
const container = containerRef.current;
|
|
8467
|
+
if (!container || readOnly) return;
|
|
8468
|
+
const onDragOver = (e) => {
|
|
8469
|
+
var _a;
|
|
8470
|
+
if ((_a = e.dataTransfer) == null ? void 0 : _a.types.includes("Files")) e.preventDefault();
|
|
8471
|
+
};
|
|
8472
|
+
const onDrop = (e) => {
|
|
8473
|
+
var _a;
|
|
8474
|
+
e.preventDefault();
|
|
8475
|
+
const file = (_a = e.dataTransfer) == null ? void 0 : _a.files[0];
|
|
8476
|
+
if (!file || !file.type.startsWith("image/")) return;
|
|
8477
|
+
const blobUrl = URL.createObjectURL(file);
|
|
8478
|
+
insertImage(blobUrl, file.name.replace(/\.[^.]+$/, ""))(engine);
|
|
8479
|
+
};
|
|
8480
|
+
container.addEventListener("dragover", onDragOver);
|
|
8481
|
+
container.addEventListener("drop", onDrop);
|
|
8482
|
+
return () => {
|
|
8483
|
+
container.removeEventListener("dragover", onDragOver);
|
|
8484
|
+
container.removeEventListener("drop", onDrop);
|
|
8485
|
+
};
|
|
8486
|
+
}, [engine, readOnly]);
|
|
8487
|
+
const isVisualEmpty = state.doc.children.length === 1 && state.doc.children[0].type === "paragraph" && state.doc.children[0].children.length === 0;
|
|
8488
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
8489
|
+
"div",
|
|
8490
|
+
{
|
|
8491
|
+
ref: editorRootRef,
|
|
8492
|
+
className: `editor-root relative flex flex-col ${className}`,
|
|
8493
|
+
style: editorHeight ? { minHeight: editorHeight } : void 0,
|
|
8494
|
+
children: [
|
|
8495
|
+
!readOnly && /* @__PURE__ */ jsxRuntime.jsx(
|
|
8496
|
+
Toolbar,
|
|
8497
|
+
{
|
|
8498
|
+
engine,
|
|
8499
|
+
onFindReplace: (mode) => {
|
|
8500
|
+
setFindReplaceMode(mode);
|
|
8501
|
+
setFindReplaceOpen(true);
|
|
8502
|
+
},
|
|
8503
|
+
linkPopupOpen,
|
|
8504
|
+
onLinkPopupClose: () => setLinkPopupOpen(false),
|
|
8505
|
+
isSourceMode,
|
|
8506
|
+
onToggleSource: handleToggleSource
|
|
8507
|
+
}
|
|
8508
|
+
),
|
|
8509
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative", children: isSourceMode ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
8510
|
+
"textarea",
|
|
8511
|
+
{
|
|
8512
|
+
"aria-label": "HTML source editor",
|
|
8513
|
+
value: sourceHTML,
|
|
8514
|
+
onChange: (e) => {
|
|
8515
|
+
setSourceHTML(e.target.value);
|
|
8516
|
+
const t = e.target;
|
|
8517
|
+
t.style.height = "auto";
|
|
8518
|
+
t.style.height = t.scrollHeight + "px";
|
|
8519
|
+
},
|
|
8520
|
+
ref: (el) => {
|
|
8521
|
+
if (el) {
|
|
8522
|
+
el.style.height = "auto";
|
|
8523
|
+
el.style.height = el.scrollHeight + "px";
|
|
8524
|
+
}
|
|
8525
|
+
},
|
|
8526
|
+
spellCheck: false,
|
|
8527
|
+
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"
|
|
8528
|
+
}
|
|
8529
|
+
) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
8530
|
+
isVisualEmpty && /* @__PURE__ */ jsxRuntime.jsx(
|
|
8531
|
+
"div",
|
|
8532
|
+
{
|
|
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 ",
|
|
8534
|
+
"aria-hidden": "true",
|
|
8535
|
+
children: placeholder
|
|
8536
|
+
}
|
|
8537
|
+
),
|
|
8538
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8539
|
+
"div",
|
|
8540
|
+
{
|
|
8541
|
+
ref: containerRef,
|
|
8542
|
+
contentEditable: !readOnly,
|
|
8543
|
+
suppressContentEditableWarning: true,
|
|
8544
|
+
role: "textbox",
|
|
8545
|
+
"aria-multiline": "true",
|
|
8546
|
+
"aria-label": "Rich text editor",
|
|
8547
|
+
spellCheck: true,
|
|
8548
|
+
onMouseDown: handleMouseDown,
|
|
8549
|
+
onContextMenu: handleContextMenu,
|
|
8550
|
+
onFocus: handleFocus,
|
|
8551
|
+
onClick: handleClick,
|
|
8552
|
+
onKeyDown: handleKeyDown,
|
|
8553
|
+
className: [
|
|
8554
|
+
"editor-canvas",
|
|
8555
|
+
"min-h-[300px]",
|
|
8556
|
+
"px-6",
|
|
8557
|
+
"py-4",
|
|
8558
|
+
"outline-none",
|
|
8559
|
+
"text-base",
|
|
8560
|
+
"leading-relaxed",
|
|
8561
|
+
"text-gray-900",
|
|
8562
|
+
"dark:text-gray-100",
|
|
8563
|
+
readOnly ? "cursor-default" : "cursor-text"
|
|
8564
|
+
].join(" ")
|
|
8565
|
+
}
|
|
8566
|
+
),
|
|
8567
|
+
linkTooltip && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
8568
|
+
"div",
|
|
8569
|
+
{
|
|
8570
|
+
role: "tooltip",
|
|
8571
|
+
style: { left: linkTooltip.x, top: linkTooltip.y },
|
|
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",
|
|
8573
|
+
children: [
|
|
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: [
|
|
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" }),
|
|
8576
|
+
/* @__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" }),
|
|
8577
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5.5 6h5", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" })
|
|
8578
|
+
] }),
|
|
8579
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: linkTooltip.href }),
|
|
8580
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 opacity-50 ml-1", children: "Ctrl+click to open" })
|
|
8581
|
+
]
|
|
8582
|
+
}
|
|
8583
|
+
)
|
|
8584
|
+
] }) }),
|
|
8585
|
+
inTableCellPos && !readOnly && /* @__PURE__ */ jsxRuntime.jsx(
|
|
8586
|
+
TableToolbar,
|
|
8587
|
+
{
|
|
8588
|
+
engine,
|
|
8589
|
+
tablePath: inTableCellPos.tablePath,
|
|
8590
|
+
cellPos: { row: inTableCellPos.row, col: inTableCellPos.col },
|
|
8591
|
+
tableSelection,
|
|
8592
|
+
editorContainer: containerRef
|
|
8593
|
+
}
|
|
8594
|
+
),
|
|
8595
|
+
tableContextMenu && /* @__PURE__ */ jsxRuntime.jsx(
|
|
8596
|
+
TableContextMenu,
|
|
8597
|
+
{
|
|
8598
|
+
x: tableContextMenu.x,
|
|
8599
|
+
y: tableContextMenu.y,
|
|
8600
|
+
tablePath: tableContextMenu.tablePath,
|
|
8601
|
+
row: tableContextMenu.row,
|
|
8602
|
+
col: tableContextMenu.col,
|
|
8603
|
+
isMerged: tableContextMenu.isMerged,
|
|
8604
|
+
tableSelection,
|
|
8605
|
+
engine,
|
|
8606
|
+
onClose: () => setTableContextMenu(null)
|
|
8607
|
+
}
|
|
8608
|
+
),
|
|
8609
|
+
findReplaceOpen && /* @__PURE__ */ jsxRuntime.jsx(
|
|
8610
|
+
FindReplaceModal,
|
|
8611
|
+
{
|
|
8612
|
+
engine,
|
|
8613
|
+
editorContainer: containerRef,
|
|
8614
|
+
initialMode: findReplaceMode,
|
|
8615
|
+
onClose: () => setFindReplaceOpen(false)
|
|
8616
|
+
}
|
|
8617
|
+
),
|
|
8618
|
+
selectedImagePath && !readOnly && /* @__PURE__ */ jsxRuntime.jsx(
|
|
8619
|
+
ImageToolbar,
|
|
8620
|
+
{
|
|
8621
|
+
engine,
|
|
8622
|
+
imagePath: selectedImagePath,
|
|
8623
|
+
editorContainer: containerRef,
|
|
8624
|
+
onClose: () => setSelectedImagePath(null)
|
|
8625
|
+
}
|
|
8626
|
+
),
|
|
8627
|
+
!readOnly && /* @__PURE__ */ jsxRuntime.jsx(
|
|
8628
|
+
"div",
|
|
8629
|
+
{
|
|
8630
|
+
title: "Drag to resize",
|
|
8631
|
+
onMouseDown: (e) => {
|
|
8632
|
+
e.preventDefault();
|
|
8633
|
+
const rootEl = editorRootRef.current;
|
|
8634
|
+
if (!rootEl) return;
|
|
8635
|
+
resizeDragRef.current = {
|
|
8636
|
+
startY: e.clientY,
|
|
8637
|
+
startHeight: rootEl.getBoundingClientRect().height
|
|
8638
|
+
};
|
|
8639
|
+
},
|
|
8640
|
+
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",
|
|
8641
|
+
"aria-hidden": "true",
|
|
8642
|
+
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" }) })
|
|
8643
|
+
}
|
|
8644
|
+
)
|
|
8645
|
+
]
|
|
8646
|
+
}
|
|
8647
|
+
);
|
|
8648
|
+
}
|
|
8649
|
+
var BLOCK_TAGS = "p|h[1-6]|ul|ol|li|blockquote|pre|figure|table|thead|tbody|tr|th|td";
|
|
8650
|
+
var _OPEN_ONLY_RE = new RegExp(`^<(${BLOCK_TAGS})(?:\\s[^>]*)?>$`, "i");
|
|
8651
|
+
var _CLOSE_ONLY_RE = new RegExp(`^<\\/(${BLOCK_TAGS})>$`, "i");
|
|
8652
|
+
var _HR_RE = /^<hr(\s[^>]*)?\/?>$/i;
|
|
8653
|
+
function leafTextNode(el, end) {
|
|
8654
|
+
let node = el;
|
|
8655
|
+
while (node.hasChildNodes()) {
|
|
8656
|
+
node = end === "first" ? node.firstChild : node.lastChild;
|
|
8657
|
+
}
|
|
8658
|
+
return node.nodeType === Node.TEXT_NODE ? node : null;
|
|
8659
|
+
}
|
|
8660
|
+
function prettyPrintHTML(html) {
|
|
8661
|
+
const blockOpen = new RegExp(`(<(?:${BLOCK_TAGS})(?:\\s[^>]*)?>)`, "gi");
|
|
8662
|
+
const blockClose = new RegExp(`(<\\/(?:${BLOCK_TAGS})>)`, "gi");
|
|
8663
|
+
const spread = html.replace(blockOpen, "\n$1").replace(blockClose, "$1\n").replace(/<hr(\s[^>]*)?\/?>(\s*)/gi, "\n<hr />\n");
|
|
8664
|
+
let depth = 0;
|
|
8665
|
+
const result = [];
|
|
8666
|
+
for (const raw of spread.split("\n")) {
|
|
8667
|
+
const line = raw.trim();
|
|
8668
|
+
if (!line) continue;
|
|
8669
|
+
if (_HR_RE.test(line)) {
|
|
8670
|
+
result.push(" ".repeat(depth) + line);
|
|
8671
|
+
continue;
|
|
8672
|
+
}
|
|
8673
|
+
if (_CLOSE_ONLY_RE.test(line)) {
|
|
8674
|
+
depth = Math.max(0, depth - 1);
|
|
8675
|
+
result.push(" ".repeat(depth) + line);
|
|
8676
|
+
continue;
|
|
8677
|
+
}
|
|
8678
|
+
if (_OPEN_ONLY_RE.test(line)) {
|
|
8679
|
+
result.push(" ".repeat(depth) + line);
|
|
8680
|
+
depth++;
|
|
8681
|
+
continue;
|
|
8682
|
+
}
|
|
8683
|
+
result.push(" ".repeat(depth) + line);
|
|
8684
|
+
}
|
|
8685
|
+
return result.join("\n");
|
|
8686
|
+
}
|
|
7836
8687
|
function EditorCore({
|
|
7837
8688
|
engine,
|
|
7838
8689
|
placeholder = "Start writing...",
|
|
@@ -7902,7 +8753,7 @@ function EditorCore({
|
|
|
7902
8753
|
const handleToggleSource = react.useCallback(() => {
|
|
7903
8754
|
if (!isSourceMode) {
|
|
7904
8755
|
const html = htmlSerializer.serialize(engine.getState().doc);
|
|
7905
|
-
setSourceHTML(
|
|
8756
|
+
setSourceHTML(prettyPrintHTML2(html));
|
|
7906
8757
|
setIsSourceMode(true);
|
|
7907
8758
|
} else {
|
|
7908
8759
|
const newDoc = htmlSerializer.deserialize(sourceHTML);
|
|
@@ -8090,8 +8941,8 @@ function EditorCore({
|
|
|
8090
8941
|
if (first && last) {
|
|
8091
8942
|
const sel = window.getSelection();
|
|
8092
8943
|
const range = document.createRange();
|
|
8093
|
-
range.setStart((_a =
|
|
8094
|
-
const lastLeaf =
|
|
8944
|
+
range.setStart((_a = leafTextNode2(first, "first")) != null ? _a : first, 0);
|
|
8945
|
+
const lastLeaf = leafTextNode2(last, "last");
|
|
8095
8946
|
range.setEnd(lastLeaf != null ? lastLeaf : last, (_c = (_b = lastLeaf == null ? void 0 : lastLeaf.textContent) == null ? void 0 : _b.length) != null ? _c : 0);
|
|
8096
8947
|
sel == null ? void 0 : sel.removeAllRanges();
|
|
8097
8948
|
sel == null ? void 0 : sel.addRange(range);
|
|
@@ -8647,36 +9498,36 @@ function EditorCore({
|
|
|
8647
9498
|
}
|
|
8648
9499
|
);
|
|
8649
9500
|
}
|
|
8650
|
-
var
|
|
8651
|
-
var
|
|
8652
|
-
var
|
|
8653
|
-
var
|
|
8654
|
-
function
|
|
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) {
|
|
8655
9506
|
let node = el;
|
|
8656
9507
|
while (node.hasChildNodes()) {
|
|
8657
9508
|
node = end === "first" ? node.firstChild : node.lastChild;
|
|
8658
9509
|
}
|
|
8659
9510
|
return node.nodeType === Node.TEXT_NODE ? node : null;
|
|
8660
9511
|
}
|
|
8661
|
-
function
|
|
8662
|
-
const blockOpen = new RegExp(`(<(?:${
|
|
8663
|
-
const blockClose = new RegExp(`(<\\/(?:${
|
|
9512
|
+
function prettyPrintHTML2(html) {
|
|
9513
|
+
const blockOpen = new RegExp(`(<(?:${BLOCK_TAGS2})(?:\\s[^>]*)?>)`, "gi");
|
|
9514
|
+
const blockClose = new RegExp(`(<\\/(?:${BLOCK_TAGS2})>)`, "gi");
|
|
8664
9515
|
const spread = html.replace(blockOpen, "\n$1").replace(blockClose, "$1\n").replace(/<hr(\s[^>]*)?\/?>(\s*)/gi, "\n<hr />\n");
|
|
8665
9516
|
let depth = 0;
|
|
8666
9517
|
const result = [];
|
|
8667
9518
|
for (const raw of spread.split("\n")) {
|
|
8668
9519
|
const line = raw.trim();
|
|
8669
9520
|
if (!line) continue;
|
|
8670
|
-
if (
|
|
9521
|
+
if (_HR_RE2.test(line)) {
|
|
8671
9522
|
result.push(" ".repeat(depth) + line);
|
|
8672
9523
|
continue;
|
|
8673
9524
|
}
|
|
8674
|
-
if (
|
|
9525
|
+
if (_CLOSE_ONLY_RE2.test(line)) {
|
|
8675
9526
|
depth = Math.max(0, depth - 1);
|
|
8676
9527
|
result.push(" ".repeat(depth) + line);
|
|
8677
9528
|
continue;
|
|
8678
9529
|
}
|
|
8679
|
-
if (
|
|
9530
|
+
if (_OPEN_ONLY_RE2.test(line)) {
|
|
8680
9531
|
result.push(" ".repeat(depth) + line);
|
|
8681
9532
|
depth++;
|
|
8682
9533
|
continue;
|
|
@@ -9148,151 +9999,6 @@ function execCommand(engine, command, ...args) {
|
|
|
9148
9999
|
function registerCommand(name, fn) {
|
|
9149
10000
|
REGISTRY[name] = fn;
|
|
9150
10001
|
}
|
|
9151
|
-
var Editor = react.forwardRef(function Editor2(props, ref) {
|
|
9152
|
-
var _a;
|
|
9153
|
-
const {
|
|
9154
|
-
value,
|
|
9155
|
-
defaultValue,
|
|
9156
|
-
onChange,
|
|
9157
|
-
placeholder,
|
|
9158
|
-
readOnly = false,
|
|
9159
|
-
toolbar = DEFAULT_TOOLBAR,
|
|
9160
|
-
theme = "light",
|
|
9161
|
-
plugins,
|
|
9162
|
-
onFocus,
|
|
9163
|
-
onBlur,
|
|
9164
|
-
onReady,
|
|
9165
|
-
onUploadImage,
|
|
9166
|
-
className,
|
|
9167
|
-
style,
|
|
9168
|
-
minHeight,
|
|
9169
|
-
maxHeight
|
|
9170
|
-
} = props;
|
|
9171
|
-
const engine = useEditorEngine();
|
|
9172
|
-
const pluginsRegisteredRef = react.useRef(false);
|
|
9173
|
-
if (!pluginsRegisteredRef.current && (plugins == null ? void 0 : plugins.length)) {
|
|
9174
|
-
pluginsRegisteredRef.current = true;
|
|
9175
|
-
for (const p of plugins) {
|
|
9176
|
-
engine.registerPlugin(p);
|
|
9177
|
-
}
|
|
9178
|
-
}
|
|
9179
|
-
const initializedRef = react.useRef(false);
|
|
9180
|
-
react.useEffect(() => {
|
|
9181
|
-
if (initializedRef.current) return;
|
|
9182
|
-
initializedRef.current = true;
|
|
9183
|
-
const initHTML = value != null ? value : defaultValue;
|
|
9184
|
-
if (!initHTML) return;
|
|
9185
|
-
const doc = htmlSerializer.deserialize(initHTML);
|
|
9186
|
-
const tr = createTransaction();
|
|
9187
|
-
tr.steps.push(tr_replaceDoc(doc));
|
|
9188
|
-
engine.dispatch(tr);
|
|
9189
|
-
}, []);
|
|
9190
|
-
const lastEmittedRef = react.useRef("");
|
|
9191
|
-
react.useEffect(() => {
|
|
9192
|
-
if (value === void 0) return;
|
|
9193
|
-
if (value === lastEmittedRef.current) return;
|
|
9194
|
-
const currentHTML = htmlSerializer.serialize(engine.getState().doc);
|
|
9195
|
-
if (value === currentHTML) return;
|
|
9196
|
-
const doc = htmlSerializer.deserialize(value);
|
|
9197
|
-
const tr = createTransaction();
|
|
9198
|
-
tr.steps.push(tr_replaceDoc(doc));
|
|
9199
|
-
engine.dispatch(tr);
|
|
9200
|
-
}, [value, engine]);
|
|
9201
|
-
const handleHTMLChange = react.useCallback((html) => {
|
|
9202
|
-
lastEmittedRef.current = html;
|
|
9203
|
-
onChange == null ? void 0 : onChange(html);
|
|
9204
|
-
}, [onChange]);
|
|
9205
|
-
const rootRef = react.useRef(null);
|
|
9206
|
-
const api = react.useMemo(() => ({
|
|
9207
|
-
getHTML: () => htmlSerializer.serialize(engine.getState().doc),
|
|
9208
|
-
setHTML: (html) => {
|
|
9209
|
-
const doc = htmlSerializer.deserialize(html);
|
|
9210
|
-
const tr = createTransaction();
|
|
9211
|
-
tr.steps.push(tr_replaceDoc(doc));
|
|
9212
|
-
engine.dispatch(tr);
|
|
9213
|
-
},
|
|
9214
|
-
getJSON: () => engine.getState().doc,
|
|
9215
|
-
getMarkdown: () => markdownSerializer.serialize(engine.getState().doc),
|
|
9216
|
-
focus: () => {
|
|
9217
|
-
var _a2;
|
|
9218
|
-
const ce = (_a2 = rootRef.current) == null ? void 0 : _a2.querySelector("[contenteditable]");
|
|
9219
|
-
ce == null ? void 0 : ce.focus();
|
|
9220
|
-
},
|
|
9221
|
-
blur: () => {
|
|
9222
|
-
var _a2;
|
|
9223
|
-
const ce = (_a2 = rootRef.current) == null ? void 0 : _a2.querySelector("[contenteditable]");
|
|
9224
|
-
ce == null ? void 0 : ce.blur();
|
|
9225
|
-
},
|
|
9226
|
-
clear: () => {
|
|
9227
|
-
const tr = createTransaction();
|
|
9228
|
-
tr.steps.push(tr_replaceDoc(createEmptyDocument()));
|
|
9229
|
-
engine.dispatch(tr);
|
|
9230
|
-
},
|
|
9231
|
-
undo: () => {
|
|
9232
|
-
engine.handleKeyDown(
|
|
9233
|
-
new KeyboardEvent("keydown", { key: "z", ctrlKey: true, bubbles: true })
|
|
9234
|
-
);
|
|
9235
|
-
},
|
|
9236
|
-
redo: () => {
|
|
9237
|
-
engine.handleKeyDown(
|
|
9238
|
-
new KeyboardEvent("keydown", { key: "y", ctrlKey: true, bubbles: true })
|
|
9239
|
-
);
|
|
9240
|
-
},
|
|
9241
|
-
execCommand: (command, ...args) => {
|
|
9242
|
-
return execCommand(engine, command, ...args);
|
|
9243
|
-
},
|
|
9244
|
-
registerCommand: (name, fn) => {
|
|
9245
|
-
registerCommand(name, fn);
|
|
9246
|
-
},
|
|
9247
|
-
getEngine: () => engine
|
|
9248
|
-
}), [engine]);
|
|
9249
|
-
react.useImperativeHandle(ref, () => api, [api]);
|
|
9250
|
-
react.useEffect(() => {
|
|
9251
|
-
onReady == null ? void 0 : onReady(api);
|
|
9252
|
-
}, []);
|
|
9253
|
-
const themeMode = typeof theme === "string" ? theme : (_a = theme.mode) != null ? _a : "light";
|
|
9254
|
-
const themeTokenOverrides = typeof theme === "object" && theme.tokens ? theme.tokens : void 0;
|
|
9255
|
-
const rootStyle = react.useMemo(() => {
|
|
9256
|
-
const base = { ...style };
|
|
9257
|
-
if (minHeight !== void 0)
|
|
9258
|
-
base.minHeight = typeof minHeight === "number" ? `${minHeight}px` : minHeight;
|
|
9259
|
-
if (maxHeight !== void 0) {
|
|
9260
|
-
base.maxHeight = typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight;
|
|
9261
|
-
base.overflow = "auto";
|
|
9262
|
-
}
|
|
9263
|
-
if (themeTokenOverrides) {
|
|
9264
|
-
for (const [key, val] of Object.entries(themeTokenOverrides)) {
|
|
9265
|
-
const cssVar = `--editor-${key.replace(/([A-Z])/g, (m) => `-${m.toLowerCase()}`)}`;
|
|
9266
|
-
base[cssVar] = val;
|
|
9267
|
-
}
|
|
9268
|
-
}
|
|
9269
|
-
return base;
|
|
9270
|
-
}, [style, minHeight, maxHeight, themeTokenOverrides]);
|
|
9271
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
9272
|
-
"div",
|
|
9273
|
-
{
|
|
9274
|
-
ref: rootRef,
|
|
9275
|
-
"data-editor-theme": themeMode,
|
|
9276
|
-
"data-traffica-editor": "",
|
|
9277
|
-
className,
|
|
9278
|
-
style: rootStyle,
|
|
9279
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
9280
|
-
EditorCore,
|
|
9281
|
-
{
|
|
9282
|
-
engine,
|
|
9283
|
-
placeholder,
|
|
9284
|
-
readOnly,
|
|
9285
|
-
hideToolbar: toolbar === false,
|
|
9286
|
-
toolbarConfig: toolbar !== false ? toolbar : void 0,
|
|
9287
|
-
onHTMLChange: handleHTMLChange,
|
|
9288
|
-
onFocus,
|
|
9289
|
-
onBlur,
|
|
9290
|
-
onUploadImage
|
|
9291
|
-
}
|
|
9292
|
-
)
|
|
9293
|
-
}
|
|
9294
|
-
);
|
|
9295
|
-
});
|
|
9296
10002
|
|
|
9297
10003
|
exports.BASIC_TOOLBAR = BASIC_TOOLBAR;
|
|
9298
10004
|
exports.DEFAULT_TOOLBAR = DEFAULT_TOOLBAR;
|