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