@lobehub/editor 4.24.1 → 4.25.0
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/es/codemirror/style.js +42 -0
- package/es/editor-kernel/kernel.js +3 -0
- package/es/editor-kernel/react/useDecorators.js +2 -4
- package/es/headless/collapsible-plugin.js +74 -0
- package/es/headless/index.js +2 -0
- package/es/headless.d.ts +18 -0
- package/es/headless.js +2479 -71
- package/es/index.d.ts +26 -3
- package/es/index.js +17 -2
- package/es/locale/index.d.ts +14 -0
- package/es/locale/index.js +14 -0
- package/es/plugins/block/command/index.js +24 -0
- package/es/plugins/block/plugin/index.js +7 -5
- package/es/plugins/block/react/ReactBlockPlugin.js +43 -1
- package/es/plugins/block/react/core/menu-position.js +20 -0
- package/es/plugins/block/react/drag/drag-session.js +3 -2
- package/es/plugins/block/react/drag/drag-utils.js +13 -1
- package/es/plugins/codemirror-block/index.d.ts +2 -1
- package/es/plugins/codemirror-block/plugin/index.js +2 -0
- package/es/plugins/codemirror-block/react/CodemirrorNode.js +95 -5
- package/es/plugins/codemirror-block/react/useCodemirrorEditLock.js +33 -0
- package/es/plugins/codemirror-block/service/index.d.ts +40 -0
- package/es/plugins/codemirror-block/service/index.js +59 -0
- package/es/plugins/collapsible/command/index.d.ts +17 -0
- package/es/plugins/collapsible/command/index.js +72 -0
- package/es/plugins/collapsible/index.d.ts +7 -0
- package/es/plugins/collapsible/node/CollapsibleNode.d.ts +30 -0
- package/es/plugins/collapsible/node/CollapsibleNode.js +138 -0
- package/es/plugins/collapsible/plugin/index.d.ts +11 -0
- package/es/plugins/collapsible/plugin/index.js +535 -0
- package/es/plugins/collapsible/react/ReactCollapsiblePlugin.d.ts +6 -0
- package/es/plugins/collapsible/react/ReactCollapsiblePlugin.js +19 -0
- package/es/plugins/collapsible/react/index.d.ts +3 -0
- package/es/plugins/collapsible/react/style.js +67 -0
- package/es/plugins/collapsible/react/type.d.ts +6 -0
- package/es/plugins/common/plugin/index.js +3 -2
- package/es/plugins/common/plugin/progressive-select-all.js +81 -0
- package/es/plugins/common/plugin/register.js +73 -4
- package/es/plugins/link/command/index.js +26 -3
- package/es/plugins/link/conversion/index.js +237 -0
- package/es/plugins/link/index.d.ts +5 -3
- package/es/plugins/link/node/LinkBlockCardNode.d.ts +16 -0
- package/es/plugins/link/node/LinkBlockCardNode.js +66 -0
- package/es/plugins/link/node/LinkCardNode.d.ts +42 -0
- package/es/plugins/link/node/LinkCardNode.js +161 -0
- package/es/plugins/link/node/LinkIframeNode.d.ts +33 -0
- package/es/plugins/link/node/LinkIframeNode.js +131 -0
- package/es/plugins/link/node/LinkNode.d.ts +48 -2
- package/es/plugins/link/node/LinkNode.js +14 -6
- package/es/plugins/link/node/SchemaNode.d.ts +36 -0
- package/es/plugins/link/node/SchemaNode.js +149 -0
- package/es/plugins/link/normalization/index.d.ts +7 -0
- package/es/plugins/link/normalization/index.js +38 -0
- package/es/plugins/link/plugin/index.d.ts +26 -1
- package/es/plugins/link/plugin/index.js +124 -4
- package/es/plugins/link/react/ReactLinkPlugin.js +165 -7
- package/es/plugins/link/react/components/LinkCard.js +235 -0
- package/es/plugins/link/react/components/LinkEdit.js +93 -14
- package/es/plugins/link/react/components/LinkIframe.js +128 -0
- package/es/plugins/link/react/components/LinkToolbar.js +440 -111
- package/es/plugins/link/react/components/SchemaLink.js +49 -0
- package/es/plugins/link/react/index.d.ts +2 -2
- package/es/plugins/link/react/renderer-registry.d.ts +81 -0
- package/es/plugins/link/react/renderer-registry.js +80 -0
- package/es/plugins/link/react/style.js +63 -1
- package/es/plugins/link/react/type.d.ts +24 -1
- package/es/plugins/link/service/i-link-service.d.ts +156 -1
- package/es/plugins/link/service/i-link-service.js +258 -5
- package/es/plugins/list/plugin/index.js +9 -2
- package/es/plugins/litexml/command/diffCommand.js +94 -14
- package/es/plugins/litexml/command/index.js +187 -6
- package/es/plugins/litexml/diff-validation.js +237 -0
- package/es/plugins/litexml/node/DiffContentNode.js +67 -0
- package/es/plugins/litexml/node/DiffNode.d.ts +1 -1
- package/es/plugins/litexml/node/DiffNode.js +5 -2
- package/es/plugins/litexml/node/TableCellDiffNode.js +77 -0
- package/es/plugins/litexml/node/TableRowDiffNode.js +73 -0
- package/es/plugins/litexml/plugin/index.d.ts +2 -0
- package/es/plugins/litexml/plugin/index.js +62 -9
- package/es/plugins/litexml/react/DiffNodeToolbar/index.js +10 -12
- package/es/plugins/litexml/react/TableRowDiffToolbar.js +123 -0
- package/es/plugins/litexml/react/hooks/useHasDiffNode.js +5 -2
- package/es/plugins/litexml/react/index.js +6 -2
- package/es/plugins/litexml/react/style.js +18 -5
- package/es/plugins/litexml/table-cell-diff.js +153 -0
- package/es/plugins/litexml/table-row-diff.js +82 -0
- package/es/plugins/markdown/data-source/markdown/parse.js +25 -1
- package/es/plugins/markdown/plugin/index.js +5 -3
- package/es/plugins/slash/react/ReactSlashPlugin.js +22 -43
- package/es/plugins/slash/react/components/DefaultSlashMenu.js +76 -16
- package/es/plugins/slash/react/menu-utils.js +84 -0
- package/es/plugins/slash/service/i-slash-service.d.ts +3 -0
- package/es/plugins/table/plugin/index.js +4 -1
- package/es/plugins/table/plugin/single-cell-paste.js +79 -0
- package/es/plugins/table/react/TableController/hooks.js +7 -4
- package/es/plugins/table/react/hooks.js +2 -2
- package/es/plugins/table/react/index.js +2 -0
- package/es/plugins/table/react/style.js +15 -5
- package/es/plugins/table/react/useScrollFocusedTableCellIntoView.js +46 -0
- package/es/plugins/toc/index.d.ts +10 -0
- package/es/plugins/toc/plugin/index.d.ts +8 -0
- package/es/plugins/toc/plugin/index.js +56 -0
- package/es/plugins/toc/react/ReactTocPlugin.d.ts +6 -0
- package/es/plugins/toc/react/ReactTocPlugin.js +36 -0
- package/es/plugins/toc/react/getNearestScrollContainer.d.ts +6 -0
- package/es/plugins/toc/react/getNearestScrollContainer.js +18 -0
- package/es/plugins/toc/react/hooks/useActiveHeading.js +59 -0
- package/es/plugins/toc/react/hooks/useTocItems.js +50 -0
- package/es/plugins/toc/react/index.d.ts +5 -0
- package/es/plugins/toc/react/type.d.ts +30 -0
- package/es/plugins/toc/react/useToc.d.ts +5 -0
- package/es/plugins/toc/react/useToc.js +41 -0
- package/es/plugins/toc/service/i-toc-service.d.ts +39 -0
- package/es/plugins/toc/service/i-toc-service.js +125 -0
- package/es/plugins/toc/service/index.d.ts +3 -0
- package/es/plugins/toc/types.d.ts +23 -0
- package/es/plugins/toc/utils/index.d.ts +6 -0
- package/es/plugins/toc/utils/index.js +30 -0
- package/es/plugins/toolbar/react/index.js +26 -12
- package/es/plugins/toolbar/react/style.js +3 -2
- package/es/plugins/virtual-block/plugin/register.js +34 -12
- package/es/plugins/yjs/index.d.ts +6 -0
- package/es/plugins/yjs/plugin/index.d.ts +7 -0
- package/es/plugins/yjs/plugin/index.js +191 -0
- package/es/plugins/yjs/plugin/types.d.ts +16 -0
- package/es/plugins/yjs/plugin/utils/awareness.js +9 -0
- package/es/plugins/yjs/plugin/utils/editor-state.js +47 -0
- package/es/plugins/yjs/plugin/utils/history.js +31 -0
- package/es/plugins/yjs/plugin/utils/node-properties.js +39 -0
- package/es/plugins/yjs/plugin/utils/sync.js +24 -0
- package/es/plugins/yjs/react/codemirrorEditLockProvider.js +59 -0
- package/es/plugins/yjs/react/index.d.ts +23 -0
- package/es/plugins/yjs/react/index.js +200 -0
- package/es/plugins/yjs/service/index.d.ts +47 -0
- package/es/plugins/yjs/service/index.js +84 -0
- package/es/plugins/yjs/websocket-provider.d.ts +78 -0
- package/es/plugins/yjs/websocket-provider.js +263 -0
- package/es/renderer/LexicalRenderer.js +9 -2
- package/es/types/kernel.d.ts +4 -0
- package/es/utils/updatePosition.js +5 -2
- package/package.json +3 -1
- package/patches/@lexical__yjs@0.42.0.patch +42 -0
- package/scripts/postinstall-lexical-patch.cjs +62 -36
package/es/headless.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { LITEXML_APPLY_COMMAND, LITEXML_DIFFNODE_ALL_COMMAND, LITEXML_DIFFNODE_COMMAND, LITEXML_INSERT_COMMAND, LITEXML_MODIFY_COMMAND, LITEXML_REMOVE_COMMAND } from "./plugins/litexml/command/symbols.js";
|
|
2
2
|
import { createHeadlessEditor as createHeadlessEditor$1 } from "@lexical/headless";
|
|
3
3
|
import { createEmptyHistoryState, registerHistory } from "@lexical/history";
|
|
4
|
-
import { $computeTableMapSkipCellCheck, $createTableNodeWithDimensions, $createTableSelection, $deleteTableColumnAtSelection, $deleteTableRowAtSelection, $findTableNode, $insertTableColumnAtSelection, $insertTableRowAtSelection, $isSimpleTable, $isTableCellNode, $isTableNode, $isTableRowNode, $isTableSelection, TableCellNode, TableNode, TableNode as TableNode$1, TableRowNode, registerTableCellUnmergeTransform, registerTablePlugin, registerTableSelectionObserver, setScrollableTablesActive } from "@lexical/table";
|
|
4
|
+
import { $computeTableMapSkipCellCheck, $createTableCellNode, $createTableNodeWithDimensions, $createTableRowNode, $createTableSelection, $deleteTableColumnAtSelection, $deleteTableRowAtSelection, $findTableNode, $insertTableColumnAtSelection, $insertTableRowAtSelection, $isSimpleTable, $isTableCellNode, $isTableNode, $isTableRowNode, $isTableSelection, TableCellNode, TableNode, TableNode as TableNode$1, TableRowNode, registerTableCellUnmergeTransform, registerTablePlugin, registerTableSelectionObserver, setScrollableTablesActive } from "@lexical/table";
|
|
5
5
|
import { get, merge, template, templateSettings } from "es-toolkit/compat";
|
|
6
6
|
import EventEmitter from "eventemitter3";
|
|
7
|
-
import { $applyNodeReplacement, $caretFromPoint, $createLineBreakNode, $createNodeSelection, $createParagraphNode, $createRangeSelection, $createTextNode, $getCaretRange, $getCharacterOffsets, $getChildCaret, $getNearestNodeFromDOMNode, $getNodeByKey, $getPreviousSelection, $getRoot, $getSelection, $insertNodes, $isBlockElementNode, $isDecoratorNode, $isElementNode, $isLineBreakNode, $isNodeSelection, $isRangeSelection, $isRootNode, $isRootOrShadowRoot, $isTextNode, $isTextPointCaret, $nodesOfType, $normalizeSelection__EXPERIMENTAL, $parseSerializedNode, $setSelection, COLLABORATION_TAG, COMMAND_PRIORITY_CRITICAL, COMMAND_PRIORITY_EDITOR, COMMAND_PRIORITY_HIGH, COMMAND_PRIORITY_LOW, COMMAND_PRIORITY_NORMAL, CONTROLLED_TEXT_INSERTION_COMMAND, COPY_COMMAND, DecoratorNode, ElementNode, FORMAT_TEXT_COMMAND, HISTORIC_TAG, HISTORY_MERGE_TAG, HISTORY_PUSH_TAG, INDENT_CONTENT_COMMAND, INSERT_LINE_BREAK_COMMAND, INSERT_PARAGRAPH_COMMAND, INSERT_TAB_COMMAND, IS_BOLD, IS_CODE, IS_ITALIC, IS_STRIKETHROUGH, IS_SUBSCRIPT, IS_SUPERSCRIPT, IS_UNDERLINE, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DOWN_COMMAND, KEY_ENTER_COMMAND, KEY_TAB_COMMAND, OUTDENT_CONTENT_COMMAND, PASTE_COMMAND, ParagraphNode, REDO_COMMAND, SELECTION_CHANGE_COMMAND, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, TEXT_TYPE_TO_FORMAT, TextNode, UNDO_COMMAND, createCommand, createEditor, getNearestEditorFromDOMNode, isHTMLElement, isModifierMatch, resetRandomKey, setDOMUnmanaged } from "lexical";
|
|
7
|
+
import { $applyNodeReplacement, $caretFromPoint, $createLineBreakNode, $createNodeSelection, $createParagraphNode, $createRangeSelection, $createTextNode, $getCaretRange, $getCharacterOffsets, $getChildCaret, $getNearestNodeFromDOMNode, $getNodeByKey, $getPreviousSelection, $getRoot, $getSelection, $insertNodes, $isBlockElementNode, $isDecoratorNode, $isElementNode, $isLineBreakNode, $isNodeSelection, $isRangeSelection, $isRootNode, $isRootOrShadowRoot, $isTextNode, $isTextPointCaret, $nodesOfType, $normalizeSelection__EXPERIMENTAL, $parseSerializedNode, $setSelection, COLLABORATION_TAG, COMMAND_PRIORITY_CRITICAL, COMMAND_PRIORITY_EDITOR, COMMAND_PRIORITY_HIGH, COMMAND_PRIORITY_LOW, COMMAND_PRIORITY_NORMAL, CONTROLLED_TEXT_INSERTION_COMMAND, COPY_COMMAND, DecoratorNode, ElementNode, FORMAT_TEXT_COMMAND, HISTORIC_TAG, HISTORY_MERGE_TAG, HISTORY_PUSH_TAG, INDENT_CONTENT_COMMAND, INSERT_LINE_BREAK_COMMAND, INSERT_PARAGRAPH_COMMAND, INSERT_TAB_COMMAND, IS_BOLD, IS_CODE, IS_ITALIC, IS_STRIKETHROUGH, IS_SUBSCRIPT, IS_SUPERSCRIPT, IS_UNDERLINE, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DOWN_COMMAND, KEY_ENTER_COMMAND, KEY_TAB_COMMAND, OUTDENT_CONTENT_COMMAND, PASTE_COMMAND, ParagraphNode, REDO_COMMAND, SELECTION_CHANGE_COMMAND, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, SELECT_ALL_COMMAND, TEXT_TYPE_TO_FORMAT, TextNode, UNDO_COMMAND, createCommand, createEditor, getNearestEditorFromDOMNode, isHTMLElement, isModifierMatch, resetRandomKey, setDOMUnmanaged } from "lexical";
|
|
8
8
|
import { $createHeadingNode, $createQuoteNode, $isHeadingNode, $isQuoteNode, HeadingNode, QuoteNode, registerRichText } from "@lexical/rich-text";
|
|
9
9
|
import createDebug from "debug";
|
|
10
10
|
import { $filter, $findMatchingParent, $getNearestBlockElementAncestorOrThrow, $getNearestNodeOfType, $insertNodeToNearestRoot, $wrapNodeInElement, CAN_USE_DOM, IS_APPLE as isApple, addClassNamesToElement, calculateZoomLevel, isHTMLAnchorElement, isHTMLElement as isHTMLElement$1, mergeRegister } from "@lexical/utils";
|
|
@@ -46,6 +46,12 @@ var locale_default = {
|
|
|
46
46
|
replace: "Replace"
|
|
47
47
|
},
|
|
48
48
|
link: {
|
|
49
|
+
copy: "Copy Link",
|
|
50
|
+
copySuccess: "Link copied",
|
|
51
|
+
convertToCard: "Convert to Card",
|
|
52
|
+
convertToIframe: "Convert to Iframe",
|
|
53
|
+
convertToLink: "Convert to Link",
|
|
54
|
+
convertToSchema: "Convert to Schema",
|
|
49
55
|
edit: "Edit Link",
|
|
50
56
|
editLinkTitle: "Link",
|
|
51
57
|
editTextTitle: "Text",
|
|
@@ -76,6 +82,14 @@ var locale_default = {
|
|
|
76
82
|
insertColumnRight: "Insert {{count}} column(s) to the right",
|
|
77
83
|
insertRowAbove: "Insert {{count}} row(s) above",
|
|
78
84
|
insertRowBelow: "Insert {{count}} row(s) below"
|
|
85
|
+
},
|
|
86
|
+
toc: {
|
|
87
|
+
ariaLabel: "Table of contents",
|
|
88
|
+
empty: "No headings",
|
|
89
|
+
expand: "Expand table of contents",
|
|
90
|
+
pin: "Pin table of contents",
|
|
91
|
+
title: "TOC",
|
|
92
|
+
unpin: "Unpin table of contents"
|
|
79
93
|
}
|
|
80
94
|
};
|
|
81
95
|
//#endregion
|
|
@@ -955,6 +969,9 @@ var Kernel = class Kernel extends EventEmitter {
|
|
|
955
969
|
this.historyState.current = null;
|
|
956
970
|
}
|
|
957
971
|
datasource.read(this.editor, content, options);
|
|
972
|
+
this.pluginsInstances.forEach((plugin) => {
|
|
973
|
+
plugin.onDocumentChange?.(type, content, options);
|
|
974
|
+
});
|
|
958
975
|
this.emit("documentChange", type, content);
|
|
959
976
|
this.logger.debug(`📥 Set ${type} document`);
|
|
960
977
|
}
|
|
@@ -1597,8 +1614,309 @@ function registerCursorNode(editor) {
|
|
|
1597
1614
|
}, COMMAND_PRIORITY_HIGH));
|
|
1598
1615
|
}
|
|
1599
1616
|
//#endregion
|
|
1617
|
+
//#region src/plugins/litexml/diff-validation.ts
|
|
1618
|
+
const ACTIONABLE_DIFF_TYPES = /* @__PURE__ */ new Set([
|
|
1619
|
+
"add",
|
|
1620
|
+
"remove",
|
|
1621
|
+
"modify",
|
|
1622
|
+
"listItemAdd",
|
|
1623
|
+
"listItemModify",
|
|
1624
|
+
"listItemRemove"
|
|
1625
|
+
]);
|
|
1626
|
+
const isActionableDiff = (node) => node?.type === "diff" && ACTIONABLE_DIFF_TYPES.has(node.diffType || "");
|
|
1627
|
+
const isElementNode = (node) => Array.isArray(node.children);
|
|
1628
|
+
const cloneNode = (node) => structuredClone(node);
|
|
1629
|
+
function hasActionableDiffDescendant(node) {
|
|
1630
|
+
return (node.children || []).some((child) => isActionableDiff(child) || hasActionableDiffDescendant(child));
|
|
1631
|
+
}
|
|
1632
|
+
/**
|
|
1633
|
+
* Validate the serialized public tree. Paths use serialized node ids, not
|
|
1634
|
+
* Lexical's private node map, so this remains stable across JSON reloads.
|
|
1635
|
+
*/
|
|
1636
|
+
function collectIllegalNestedDiffPaths(root) {
|
|
1637
|
+
const violations = [];
|
|
1638
|
+
const visit = (node, ancestors, path) => {
|
|
1639
|
+
const actionableAncestor = [...ancestors].reverse().find(isActionableDiff);
|
|
1640
|
+
const rowDiffAncestor = [...ancestors].reverse().find((ancestor) => ancestor.type === "table-row-diff");
|
|
1641
|
+
const allowedCellDiffChild = ancestors.at(-1)?.type === "table-cell-diff" && isActionableDiff(node);
|
|
1642
|
+
if (isActionableDiff(node) && actionableAncestor && !allowedCellDiffChild) violations.push(`${path}:diff-inside-diff`);
|
|
1643
|
+
if (rowDiffAncestor) {
|
|
1644
|
+
if (node.type === "table-cell-diff") violations.push(`${path}:cell-inside-row-diff`);
|
|
1645
|
+
else if (isActionableDiff(node)) violations.push(`${path}:diff-inside-row-diff`);
|
|
1646
|
+
}
|
|
1647
|
+
if (actionableAncestor) {
|
|
1648
|
+
if (node.type === "table-cell-diff") violations.push(`${path}:cell-diff-inside-diff`);
|
|
1649
|
+
else if (node.type === "table-row-diff") violations.push(`${path}:row-diff-inside-diff`);
|
|
1650
|
+
}
|
|
1651
|
+
if (!isElementNode(node)) return;
|
|
1652
|
+
node.children?.forEach((child) => {
|
|
1653
|
+
visit(child, [...ancestors, node], `${path}/${child.id || child.type || "node"}`);
|
|
1654
|
+
});
|
|
1655
|
+
};
|
|
1656
|
+
visit(root, [], root.id || root.type || "root");
|
|
1657
|
+
return violations;
|
|
1658
|
+
}
|
|
1659
|
+
function findNewIllegalDiffPaths(previous, projected) {
|
|
1660
|
+
const previousCounts = /* @__PURE__ */ new Map();
|
|
1661
|
+
collectIllegalNestedDiffPaths(previous.root).forEach((path) => {
|
|
1662
|
+
previousCounts.set(path, (previousCounts.get(path) || 0) + 1);
|
|
1663
|
+
});
|
|
1664
|
+
const newPaths = [];
|
|
1665
|
+
const projectedCounts = /* @__PURE__ */ new Map();
|
|
1666
|
+
collectIllegalNestedDiffPaths(projected.root).forEach((path) => {
|
|
1667
|
+
const count = (projectedCounts.get(path) || 0) + 1;
|
|
1668
|
+
projectedCounts.set(path, count);
|
|
1669
|
+
if (count > (previousCounts.get(path) || 0)) newPaths.push(path);
|
|
1670
|
+
});
|
|
1671
|
+
return newPaths;
|
|
1672
|
+
}
|
|
1673
|
+
function findNodeLocation(root, id, ancestors = []) {
|
|
1674
|
+
if (root.id === id) return {
|
|
1675
|
+
ancestors,
|
|
1676
|
+
index: -1,
|
|
1677
|
+
node: root,
|
|
1678
|
+
parent: null
|
|
1679
|
+
};
|
|
1680
|
+
for (const [index, child] of (root.children || []).entries()) {
|
|
1681
|
+
if (child.id === id) return {
|
|
1682
|
+
ancestors: [...ancestors, root],
|
|
1683
|
+
index,
|
|
1684
|
+
node: child,
|
|
1685
|
+
parent: root
|
|
1686
|
+
};
|
|
1687
|
+
const nested = findNodeLocation(child, id, [...ancestors, root]);
|
|
1688
|
+
if (nested) return nested;
|
|
1689
|
+
}
|
|
1690
|
+
return null;
|
|
1691
|
+
}
|
|
1692
|
+
function replaceNode(location, replacements) {
|
|
1693
|
+
if (!location.parent || location.index < 0) return;
|
|
1694
|
+
location.parent.children?.splice(location.index, 1, ...replacements);
|
|
1695
|
+
}
|
|
1696
|
+
function createDiff(diffType, children) {
|
|
1697
|
+
return {
|
|
1698
|
+
children,
|
|
1699
|
+
diffType,
|
|
1700
|
+
type: "diff"
|
|
1701
|
+
};
|
|
1702
|
+
}
|
|
1703
|
+
function createDiffContent(side, children) {
|
|
1704
|
+
return {
|
|
1705
|
+
children,
|
|
1706
|
+
side,
|
|
1707
|
+
type: "diff-content"
|
|
1708
|
+
};
|
|
1709
|
+
}
|
|
1710
|
+
function projectModifyTarget(root, incoming) {
|
|
1711
|
+
if (!incoming.id) return;
|
|
1712
|
+
const location = findNodeLocation(root, incoming.id);
|
|
1713
|
+
if (!location) return;
|
|
1714
|
+
const target = location.node;
|
|
1715
|
+
const next = cloneNode(incoming);
|
|
1716
|
+
if (target.type === "tablerow" && location.parent?.type === "table") {
|
|
1717
|
+
replaceNode(location, [{
|
|
1718
|
+
children: cloneNode(target.children || []),
|
|
1719
|
+
diffType: "remove",
|
|
1720
|
+
type: "table-row-diff"
|
|
1721
|
+
}, {
|
|
1722
|
+
children: cloneNode(next.children || []),
|
|
1723
|
+
diffType: "add",
|
|
1724
|
+
type: "table-row-diff"
|
|
1725
|
+
}]);
|
|
1726
|
+
return;
|
|
1727
|
+
}
|
|
1728
|
+
if (target.type === "tablecell" || target.type === "table-cell-diff") {
|
|
1729
|
+
const existingDiff = (target.children || []).find((child) => child.type === "diff");
|
|
1730
|
+
if (existingDiff && target.type === "table-cell-diff") {
|
|
1731
|
+
existingDiff.children = cloneNode(next.children || []);
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1734
|
+
const after = existingDiff?.children?.find((child) => child.type === "diff-content" && child.side === "after");
|
|
1735
|
+
if (existingDiff?.diffType === "modify" && after) {
|
|
1736
|
+
after.children = cloneNode(next.children || []);
|
|
1737
|
+
return;
|
|
1738
|
+
}
|
|
1739
|
+
target.children = [createDiff("modify", [createDiffContent("before", cloneNode(target.children || [])), createDiffContent("after", cloneNode(next.children || []))])];
|
|
1740
|
+
return;
|
|
1741
|
+
}
|
|
1742
|
+
if (location.ancestors.some(isActionableDiff)) {
|
|
1743
|
+
replaceNode(location, [next]);
|
|
1744
|
+
return;
|
|
1745
|
+
}
|
|
1746
|
+
replaceNode(location, [createDiff("modify", [cloneNode(target), next])]);
|
|
1747
|
+
}
|
|
1748
|
+
function projectRemoveTarget(root, id) {
|
|
1749
|
+
const location = findNodeLocation(root, id);
|
|
1750
|
+
if (!location) return;
|
|
1751
|
+
if (location.ancestors.some(isActionableDiff)) {
|
|
1752
|
+
replaceNode(location, []);
|
|
1753
|
+
return;
|
|
1754
|
+
}
|
|
1755
|
+
const target = location.node;
|
|
1756
|
+
if (target.type === "tablecell" || target.type === "table-cell-diff") {
|
|
1757
|
+
replaceNode(location, [{
|
|
1758
|
+
children: [createDiff("remove", cloneNode(target.children || []))],
|
|
1759
|
+
diffType: "remove",
|
|
1760
|
+
type: "table-cell-diff"
|
|
1761
|
+
}]);
|
|
1762
|
+
return;
|
|
1763
|
+
}
|
|
1764
|
+
if (target.type === "tablerow" && location.parent?.type === "table") {
|
|
1765
|
+
replaceNode(location, [{
|
|
1766
|
+
children: cloneNode(target.children || []),
|
|
1767
|
+
diffType: "remove",
|
|
1768
|
+
type: "table-row-diff"
|
|
1769
|
+
}]);
|
|
1770
|
+
return;
|
|
1771
|
+
}
|
|
1772
|
+
replaceNode(location, [createDiff("remove", [cloneNode(target)])]);
|
|
1773
|
+
}
|
|
1774
|
+
function projectInsert(root, operation, readXml) {
|
|
1775
|
+
const referenceId = "beforeId" in operation ? operation.beforeId : operation.afterId;
|
|
1776
|
+
const reference = referenceId === "root" ? null : findNodeLocation(root, referenceId)?.node || null;
|
|
1777
|
+
const location = referenceId === "root" ? {
|
|
1778
|
+
ancestors: [root],
|
|
1779
|
+
index: "beforeId" in operation ? 0 : (root.children || []).length - 1,
|
|
1780
|
+
node: root.children?.[0] || root,
|
|
1781
|
+
parent: root
|
|
1782
|
+
} : findNodeLocation(root, referenceId);
|
|
1783
|
+
if (!location) return;
|
|
1784
|
+
const incoming = (readXml(operation.litexml).root?.children || []).map(cloneNode);
|
|
1785
|
+
if (incoming.length === 0 || !location.parent) return;
|
|
1786
|
+
const insertAt = "beforeId" in operation ? location.index : location.index + 1;
|
|
1787
|
+
if (incoming.every((node) => node.type === "tablecell") && reference?.type === "tablecell") {
|
|
1788
|
+
location.parent.children?.splice(insertAt, 0, ...incoming.map((node) => ({
|
|
1789
|
+
children: [createDiff("add", cloneNode(node.children || []))],
|
|
1790
|
+
diffType: "add",
|
|
1791
|
+
type: "table-cell-diff"
|
|
1792
|
+
})));
|
|
1793
|
+
return;
|
|
1794
|
+
}
|
|
1795
|
+
if (incoming.every((node) => node.type === "tablerow") && reference?.type === "tablerow") {
|
|
1796
|
+
location.parent.children?.splice(insertAt, 0, ...incoming.map((node) => ({
|
|
1797
|
+
children: cloneNode(node.children || []),
|
|
1798
|
+
diffType: "add",
|
|
1799
|
+
type: "table-row-diff"
|
|
1800
|
+
})));
|
|
1801
|
+
return;
|
|
1802
|
+
}
|
|
1803
|
+
const actionableAncestor = location.ancestors.findLast(isActionableDiff);
|
|
1804
|
+
const isInlineReference = [
|
|
1805
|
+
"text",
|
|
1806
|
+
"span",
|
|
1807
|
+
"link",
|
|
1808
|
+
"autolink",
|
|
1809
|
+
"linebreak"
|
|
1810
|
+
].includes(reference?.type || "");
|
|
1811
|
+
const blockAncestor = [...location.ancestors].reverse().find((node) => [
|
|
1812
|
+
"paragraph",
|
|
1813
|
+
"heading",
|
|
1814
|
+
"listitem",
|
|
1815
|
+
"table",
|
|
1816
|
+
"tablerow",
|
|
1817
|
+
"tablecell"
|
|
1818
|
+
].includes(node.type || ""));
|
|
1819
|
+
if (!actionableAncestor && blockAncestor && hasActionableDiffDescendant(blockAncestor)) {
|
|
1820
|
+
const wrapped = createDiff("modify", [cloneNode(blockAncestor), cloneNode(blockAncestor)]);
|
|
1821
|
+
const blockLocation = findNodeLocation(root, blockAncestor.id || "");
|
|
1822
|
+
if (blockLocation) replaceNode(blockLocation, [wrapped]);
|
|
1823
|
+
return;
|
|
1824
|
+
}
|
|
1825
|
+
if (actionableAncestor && !isInlineReference) {
|
|
1826
|
+
const originLocation = actionableAncestor.id ? findNodeLocation(root, actionableAncestor.id) : null;
|
|
1827
|
+
if (originLocation?.parent && originLocation.index >= 0) {
|
|
1828
|
+
const originInsertAt = "beforeId" in operation ? originLocation.index : originLocation.index + 1;
|
|
1829
|
+
originLocation.parent.children?.splice(originInsertAt, 0, ...incoming.map((node) => createDiff("add", [node])));
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
const inserted = actionableAncestor && isInlineReference ? incoming : incoming.map((node) => createDiff("add", [node]));
|
|
1834
|
+
location.parent.children?.splice(insertAt, 0, ...inserted);
|
|
1835
|
+
}
|
|
1836
|
+
/**
|
|
1837
|
+
* Project one operation on a cloned serialized tree. The real Lexical tree is
|
|
1838
|
+
* untouched; callers compare illegal paths before/after and only dispatch safe
|
|
1839
|
+
* operations to the live editor.
|
|
1840
|
+
*/
|
|
1841
|
+
function projectLiteXmlOperation(document, operation, readXml) {
|
|
1842
|
+
const projected = cloneNode(document);
|
|
1843
|
+
if (operation.action === "modify") (Array.isArray(operation.litexml) ? operation.litexml : [operation.litexml]).forEach((xml) => {
|
|
1844
|
+
(readXml(xml).root?.children || []).forEach((child) => {
|
|
1845
|
+
projectModifyTarget(projected.root, child);
|
|
1846
|
+
});
|
|
1847
|
+
});
|
|
1848
|
+
else if (operation.action === "remove") projectRemoveTarget(projected.root, operation.id);
|
|
1849
|
+
else projectInsert(projected.root, operation, readXml);
|
|
1850
|
+
return projected;
|
|
1851
|
+
}
|
|
1852
|
+
//#endregion
|
|
1853
|
+
//#region src/plugins/litexml/node/DiffContentNode.ts
|
|
1854
|
+
/** Groups one side of a modify diff so it can contain any number of block nodes. */
|
|
1855
|
+
var DiffContentNode = class DiffContentNode extends ElementNode {
|
|
1856
|
+
static getType() {
|
|
1857
|
+
return "diff-content";
|
|
1858
|
+
}
|
|
1859
|
+
static clone(node) {
|
|
1860
|
+
return new DiffContentNode(node.__side, node.__key);
|
|
1861
|
+
}
|
|
1862
|
+
static importJSON(serializedNode) {
|
|
1863
|
+
return $createDiffContentNode(serializedNode.side).updateFromJSON(serializedNode);
|
|
1864
|
+
}
|
|
1865
|
+
static importDOM() {
|
|
1866
|
+
return null;
|
|
1867
|
+
}
|
|
1868
|
+
constructor(side, key) {
|
|
1869
|
+
super(key);
|
|
1870
|
+
this.__side = side;
|
|
1871
|
+
}
|
|
1872
|
+
get side() {
|
|
1873
|
+
return this.__side;
|
|
1874
|
+
}
|
|
1875
|
+
setSide(side) {
|
|
1876
|
+
this.getWritable().__side = side;
|
|
1877
|
+
return this;
|
|
1878
|
+
}
|
|
1879
|
+
updateFromJSON(serializedNode) {
|
|
1880
|
+
return super.updateFromJSON(serializedNode).setSide(serializedNode.side);
|
|
1881
|
+
}
|
|
1882
|
+
exportJSON() {
|
|
1883
|
+
return {
|
|
1884
|
+
...super.exportJSON(),
|
|
1885
|
+
side: this.__side,
|
|
1886
|
+
type: DiffContentNode.getType(),
|
|
1887
|
+
version: 1
|
|
1888
|
+
};
|
|
1889
|
+
}
|
|
1890
|
+
createDOM(_config, _editor) {
|
|
1891
|
+
const element = document.createElement("div");
|
|
1892
|
+
element.dataset.diffContent = this.__side;
|
|
1893
|
+
return element;
|
|
1894
|
+
}
|
|
1895
|
+
updateDOM(previousNode, dom) {
|
|
1896
|
+
if (previousNode.__side !== this.__side) dom.dataset.diffContent = this.__side;
|
|
1897
|
+
return false;
|
|
1898
|
+
}
|
|
1899
|
+
exportDOM(_editor) {
|
|
1900
|
+
const element = document.createElement("div");
|
|
1901
|
+
element.dataset.diffContent = this.__side;
|
|
1902
|
+
return { element };
|
|
1903
|
+
}
|
|
1904
|
+
canBeEmpty() {
|
|
1905
|
+
return true;
|
|
1906
|
+
}
|
|
1907
|
+
isInline() {
|
|
1908
|
+
return false;
|
|
1909
|
+
}
|
|
1910
|
+
};
|
|
1911
|
+
function $createDiffContentNode(side) {
|
|
1912
|
+
return $applyNodeReplacement(new DiffContentNode(side));
|
|
1913
|
+
}
|
|
1914
|
+
function $isDiffContentNode(node) {
|
|
1915
|
+
return node instanceof DiffContentNode;
|
|
1916
|
+
}
|
|
1917
|
+
//#endregion
|
|
1600
1918
|
//#region src/plugins/litexml/node/DiffNode.ts
|
|
1601
|
-
/** DiffNode - contains
|
|
1919
|
+
/** DiffNode - contains the original and modified sides, optionally grouped into multiple blocks. */
|
|
1602
1920
|
var DiffNode = class DiffNode extends CardLikeElementNode {
|
|
1603
1921
|
static getType() {
|
|
1604
1922
|
return "diff";
|
|
@@ -1690,6 +2008,83 @@ var DiffNode = class DiffNode extends CardLikeElementNode {
|
|
|
1690
2008
|
function $createDiffNode(diffType = "unchanged") {
|
|
1691
2009
|
return $applyNodeReplacement(new DiffNode(diffType));
|
|
1692
2010
|
}
|
|
2011
|
+
function $isDiffNode(node) {
|
|
2012
|
+
return node instanceof DiffNode;
|
|
2013
|
+
}
|
|
2014
|
+
//#endregion
|
|
2015
|
+
//#region src/plugins/litexml/node/TableCellDiffNode.ts
|
|
2016
|
+
/**
|
|
2017
|
+
* Review-only table cell. The diff state lives on a TableCellNode subclass so
|
|
2018
|
+
* TableRowNode children remain cells throughout staging and approval.
|
|
2019
|
+
*/
|
|
2020
|
+
var TableCellDiffNode = class TableCellDiffNode extends TableCellNode {
|
|
2021
|
+
static getType() {
|
|
2022
|
+
return "table-cell-diff";
|
|
2023
|
+
}
|
|
2024
|
+
static clone(node) {
|
|
2025
|
+
const clone = new TableCellDiffNode(node.__diffType, node.__changeId, node.getHeaderStyles(), node.getColSpan(), node.getWidth(), node.__key);
|
|
2026
|
+
clone.__rowSpan = node.__rowSpan;
|
|
2027
|
+
clone.__backgroundColor = node.__backgroundColor;
|
|
2028
|
+
clone.__verticalAlign = node.__verticalAlign;
|
|
2029
|
+
return clone;
|
|
2030
|
+
}
|
|
2031
|
+
static importJSON(serializedNode) {
|
|
2032
|
+
return $createTableCellDiffNode(serializedNode.diffType, serializedNode.changeId, serializedNode.headerState, serializedNode.colSpan, serializedNode.width).updateFromJSON(serializedNode);
|
|
2033
|
+
}
|
|
2034
|
+
constructor(diffType, changeId, headerState, colSpan, width, key) {
|
|
2035
|
+
super(headerState, colSpan, width, key);
|
|
2036
|
+
this.__diffType = diffType;
|
|
2037
|
+
this.__changeId = changeId;
|
|
2038
|
+
}
|
|
2039
|
+
createDOM(config) {
|
|
2040
|
+
const element = super.createDOM(config);
|
|
2041
|
+
const theme = config.theme.tableCellDiff;
|
|
2042
|
+
if (typeof theme === "string" && theme) element.classList.add(theme);
|
|
2043
|
+
this.syncDOMState(element);
|
|
2044
|
+
return element;
|
|
2045
|
+
}
|
|
2046
|
+
updateDOM(prevNode) {
|
|
2047
|
+
return super.updateDOM(prevNode) || prevNode.__diffType !== this.__diffType || prevNode.__changeId !== this.__changeId;
|
|
2048
|
+
}
|
|
2049
|
+
exportJSON() {
|
|
2050
|
+
return {
|
|
2051
|
+
...super.exportJSON(),
|
|
2052
|
+
changeId: this.__changeId,
|
|
2053
|
+
diffType: this.__diffType,
|
|
2054
|
+
type: TableCellDiffNode.getType(),
|
|
2055
|
+
version: 1
|
|
2056
|
+
};
|
|
2057
|
+
}
|
|
2058
|
+
getChangeId() {
|
|
2059
|
+
return this.getLatest().__changeId;
|
|
2060
|
+
}
|
|
2061
|
+
getDiffType() {
|
|
2062
|
+
return this.getLatest().__diffType;
|
|
2063
|
+
}
|
|
2064
|
+
setChangeId(changeId) {
|
|
2065
|
+
this.getWritable().__changeId = changeId;
|
|
2066
|
+
return this;
|
|
2067
|
+
}
|
|
2068
|
+
setDiffType(diffType) {
|
|
2069
|
+
this.getWritable().__diffType = diffType;
|
|
2070
|
+
return this;
|
|
2071
|
+
}
|
|
2072
|
+
updateFromJSON(serializedNode) {
|
|
2073
|
+
return super.updateFromJSON(serializedNode).setDiffType(serializedNode.diffType).setChangeId(serializedNode.changeId);
|
|
2074
|
+
}
|
|
2075
|
+
syncDOMState(element) {
|
|
2076
|
+
element.contentEditable = "false";
|
|
2077
|
+
element.dataset.diffType = this.__diffType;
|
|
2078
|
+
if (this.__changeId) element.dataset.diffChangeId = this.__changeId;
|
|
2079
|
+
else delete element.dataset.diffChangeId;
|
|
2080
|
+
}
|
|
2081
|
+
};
|
|
2082
|
+
function $createTableCellDiffNode(diffType, changeId, headerState, colSpan, width) {
|
|
2083
|
+
return $applyNodeReplacement(new TableCellDiffNode(diffType, changeId, headerState, colSpan, width));
|
|
2084
|
+
}
|
|
2085
|
+
function $isTableCellDiffNode(node) {
|
|
2086
|
+
return node instanceof TableCellDiffNode;
|
|
2087
|
+
}
|
|
1693
2088
|
//#endregion
|
|
1694
2089
|
//#region src/plugins/litexml/utils/index.ts
|
|
1695
2090
|
const getNumericId$1 = (id) => {
|
|
@@ -1748,11 +2143,333 @@ function charToId(char) {
|
|
|
1748
2143
|
return String((parseInt(char, 36) - startId + maxId) * modInverse % maxId);
|
|
1749
2144
|
}
|
|
1750
2145
|
//#endregion
|
|
2146
|
+
//#region src/plugins/litexml/table-cell-diff.ts
|
|
2147
|
+
function copyCellStructure(source, target) {
|
|
2148
|
+
target.setRowSpan(source.getRowSpan());
|
|
2149
|
+
target.setBackgroundColor(source.getBackgroundColor());
|
|
2150
|
+
target.setVerticalAlign(source.getVerticalAlign());
|
|
2151
|
+
}
|
|
2152
|
+
function $createTableCellDiffFromCell(editor, cell, diffType, changeId) {
|
|
2153
|
+
const diffCell = $createTableCellDiffNode(diffType, changeId, cell.getHeaderStyles(), cell.getColSpan(), cell.getWidth());
|
|
2154
|
+
copyCellStructure(cell, diffCell);
|
|
2155
|
+
const diff = $createDiffNode(diffType);
|
|
2156
|
+
diff.append(...cell.getChildren().map((child) => $cloneNode(child, editor)));
|
|
2157
|
+
diffCell.append(diff);
|
|
2158
|
+
return diffCell;
|
|
2159
|
+
}
|
|
2160
|
+
function $createPlainTableCellFromDiff(editor, cell) {
|
|
2161
|
+
const plainCell = $createTableCellNode(cell.getHeaderStyles(), cell.getColSpan(), cell.getWidth());
|
|
2162
|
+
copyCellStructure(cell, plainCell);
|
|
2163
|
+
const diff = cell.getFirstChild();
|
|
2164
|
+
if (diff instanceof DiffNode) plainCell.append(...diff.getChildren().map((child) => $cloneNode(child, editor)));
|
|
2165
|
+
else plainCell.append(...cell.getChildren().map((child) => $cloneNode(child, editor)));
|
|
2166
|
+
return plainCell;
|
|
2167
|
+
}
|
|
2168
|
+
function $getTableForCell(cell) {
|
|
2169
|
+
const row = cell.getParent();
|
|
2170
|
+
if (!$isTableRowNode(row)) return null;
|
|
2171
|
+
const table = row.getParent();
|
|
2172
|
+
return $isTableNode(table) ? table : null;
|
|
2173
|
+
}
|
|
2174
|
+
function $getTableCellColumnIndex(cell) {
|
|
2175
|
+
const row = cell.getParent();
|
|
2176
|
+
if (!$isTableRowNode(row)) return -1;
|
|
2177
|
+
let index = 0;
|
|
2178
|
+
for (const sibling of row.getChildren()) {
|
|
2179
|
+
if (sibling === cell) return index;
|
|
2180
|
+
if ($isTableCellNode(sibling)) index += sibling.getColSpan();
|
|
2181
|
+
}
|
|
2182
|
+
return -1;
|
|
2183
|
+
}
|
|
2184
|
+
function $getLogicalRowWidth(cell) {
|
|
2185
|
+
const row = cell.getParent();
|
|
2186
|
+
if (!$isTableRowNode(row)) return 0;
|
|
2187
|
+
return row.getChildren().reduce((total, child) => total + ($isTableCellNode(child) ? child.getColSpan() : 0), 0);
|
|
2188
|
+
}
|
|
2189
|
+
function $getTableCellDiffGroup(cell) {
|
|
2190
|
+
const changeId = cell.getChangeId();
|
|
2191
|
+
const table = $getTableForCell(cell);
|
|
2192
|
+
if (!changeId || !table) return [cell];
|
|
2193
|
+
return $nodesOfType(TableCellDiffNode).filter((candidate) => candidate.getChangeId() === changeId && candidate.getDiffType() === cell.getDiffType() && $getTableForCell(candidate) === table);
|
|
2194
|
+
}
|
|
2195
|
+
function $updateTableWidthsForCellInsertion(table, rowWidthBefore, columnIndex, insertedSpan) {
|
|
2196
|
+
const currentWidths = table.getColWidths() || Array.from({ length: rowWidthBefore }, () => 250);
|
|
2197
|
+
if (rowWidthBefore < currentWidths.length) return;
|
|
2198
|
+
const fallback = currentWidths[Math.max(0, columnIndex - 1)] || 250;
|
|
2199
|
+
const next = [...currentWidths];
|
|
2200
|
+
next.splice(columnIndex, 0, ...Array.from({ length: insertedSpan }, () => fallback));
|
|
2201
|
+
table.setColWidths(next);
|
|
2202
|
+
}
|
|
2203
|
+
function $removeTableWidthsForCompleteCellGroup(table, group, columnIndex, span) {
|
|
2204
|
+
const rows = table.getChildren().filter($isTableRowNode);
|
|
2205
|
+
const rowKeys = new Set(group.map((cell) => cell.getParent()?.getKey()));
|
|
2206
|
+
if (rows.length !== rowKeys.size) return;
|
|
2207
|
+
const widths = table.getColWidths();
|
|
2208
|
+
if (!widths) return;
|
|
2209
|
+
const next = [...widths];
|
|
2210
|
+
next.splice(columnIndex, span);
|
|
2211
|
+
table.setColWidths(next);
|
|
2212
|
+
}
|
|
2213
|
+
function $shrinkTableWidthsAfterCellRemoval(table, columnIndex, span) {
|
|
2214
|
+
const widths = table.getColWidths();
|
|
2215
|
+
if (!widths) return;
|
|
2216
|
+
if (table.getChildren().reduce((max, row) => {
|
|
2217
|
+
if (!$isTableRowNode(row)) return max;
|
|
2218
|
+
return Math.max(max, row.getChildren().reduce((total, cell) => total + ($isTableCellNode(cell) ? cell.getColSpan() : 0), 0));
|
|
2219
|
+
}, 0) > widths.length - span) return;
|
|
2220
|
+
const next = [...widths];
|
|
2221
|
+
next.splice(columnIndex, span);
|
|
2222
|
+
table.setColWidths(next);
|
|
2223
|
+
}
|
|
2224
|
+
function $getMaximumLogicalColumnCount(table) {
|
|
2225
|
+
return table.getChildren().reduce((maximum, row) => {
|
|
2226
|
+
if (!$isTableRowNode(row)) return maximum;
|
|
2227
|
+
const columnCount = row.getChildren().reduce((total, cell) => {
|
|
2228
|
+
return total + ($isTableCellNode(cell) ? cell.getColSpan() : 0);
|
|
2229
|
+
}, 0);
|
|
2230
|
+
return Math.max(maximum, columnCount);
|
|
2231
|
+
}, 0);
|
|
2232
|
+
}
|
|
2233
|
+
function $hasLegacyTableCellDiffs() {
|
|
2234
|
+
return $nodesOfType(DiffNode).some((diff) => {
|
|
2235
|
+
return (diff.diffType === "add" || diff.diffType === "remove") && $isTableRowNode(diff.getParent()) && diff.getChildren().some($isTableCellNode);
|
|
2236
|
+
});
|
|
2237
|
+
}
|
|
2238
|
+
function $hasIncompleteTableWidths() {
|
|
2239
|
+
return $nodesOfType(TableNode).some((table) => {
|
|
2240
|
+
return (table.getColWidths()?.length || 0) < $getMaximumLogicalColumnCount(table);
|
|
2241
|
+
});
|
|
2242
|
+
}
|
|
2243
|
+
/**
|
|
2244
|
+
* Migrates the old `TableRow > DiffNode > TableCellNode` representation into
|
|
2245
|
+
* cell-shaped diff nodes and makes sure the table has a width for every
|
|
2246
|
+
* logical column. Safe to call repeatedly.
|
|
2247
|
+
*/
|
|
2248
|
+
function $normalizeLegacyTableCellDiffs(editor) {
|
|
2249
|
+
let changed = false;
|
|
2250
|
+
$nodesOfType(DiffNode).forEach((legacyDiff) => {
|
|
2251
|
+
const diffType = legacyDiff.diffType;
|
|
2252
|
+
if (diffType !== "add" && diffType !== "remove" || !$isTableRowNode(legacyDiff.getParent())) return;
|
|
2253
|
+
const cells = legacyDiff.getChildren().filter($isTableCellNode);
|
|
2254
|
+
if (cells.length === 0) return;
|
|
2255
|
+
const changeId = `legacy-table-cell-${legacyDiff.getKey()}`;
|
|
2256
|
+
cells.forEach((cell) => {
|
|
2257
|
+
legacyDiff.insertBefore($createTableCellDiffFromCell(editor, cell, diffType, changeId));
|
|
2258
|
+
});
|
|
2259
|
+
legacyDiff.remove();
|
|
2260
|
+
changed = true;
|
|
2261
|
+
});
|
|
2262
|
+
$nodesOfType(TableNode).forEach((table) => {
|
|
2263
|
+
const requiredWidthCount = $getMaximumLogicalColumnCount(table);
|
|
2264
|
+
const currentWidths = table.getColWidths() || [];
|
|
2265
|
+
if (currentWidths.length >= requiredWidthCount) return;
|
|
2266
|
+
const fallback = currentWidths.at(-1) || 250;
|
|
2267
|
+
table.setColWidths([...currentWidths, ...Array.from({ length: requiredWidthCount - currentWidths.length }, () => fallback)]);
|
|
2268
|
+
changed = true;
|
|
2269
|
+
});
|
|
2270
|
+
return changed;
|
|
2271
|
+
}
|
|
2272
|
+
function registerLegacyTableCellDiffNormalization(editor) {
|
|
2273
|
+
let destroyed = false;
|
|
2274
|
+
let scheduled = false;
|
|
2275
|
+
const unregister = editor.registerUpdateListener(() => {
|
|
2276
|
+
if (scheduled || destroyed) return;
|
|
2277
|
+
scheduled = true;
|
|
2278
|
+
queueMicrotask(() => {
|
|
2279
|
+
scheduled = false;
|
|
2280
|
+
if (destroyed) return;
|
|
2281
|
+
if (!editor.getEditorState().read(() => $hasLegacyTableCellDiffs() || $hasIncompleteTableWidths())) return;
|
|
2282
|
+
editor.update(() => {
|
|
2283
|
+
$normalizeLegacyTableCellDiffs(editor);
|
|
2284
|
+
}, { discrete: true });
|
|
2285
|
+
});
|
|
2286
|
+
});
|
|
2287
|
+
return () => {
|
|
2288
|
+
destroyed = true;
|
|
2289
|
+
unregister();
|
|
2290
|
+
};
|
|
2291
|
+
}
|
|
2292
|
+
//#endregion
|
|
2293
|
+
//#region src/plugins/litexml/node/TableRowDiffNode.ts
|
|
2294
|
+
/**
|
|
2295
|
+
* A review-only table row. Keeping the diff state on a TableRowNode subclass
|
|
2296
|
+
* preserves Lexical's table invariant: TableNode children are always rows.
|
|
2297
|
+
*/
|
|
2298
|
+
var TableRowDiffNode = class TableRowDiffNode extends TableRowNode {
|
|
2299
|
+
static getType() {
|
|
2300
|
+
return "table-row-diff";
|
|
2301
|
+
}
|
|
2302
|
+
static clone(node) {
|
|
2303
|
+
return new TableRowDiffNode(node.__diffType, node.__changeId, node.getHeight(), node.__key);
|
|
2304
|
+
}
|
|
2305
|
+
static importJSON(serializedNode) {
|
|
2306
|
+
return $createTableRowDiffNode(serializedNode.diffType, serializedNode.changeId, serializedNode.height).updateFromJSON(serializedNode);
|
|
2307
|
+
}
|
|
2308
|
+
constructor(diffType, changeId, height, key) {
|
|
2309
|
+
super(height, key);
|
|
2310
|
+
this.__diffType = diffType;
|
|
2311
|
+
this.__changeId = changeId;
|
|
2312
|
+
}
|
|
2313
|
+
createDOM(config) {
|
|
2314
|
+
const element = super.createDOM(config);
|
|
2315
|
+
const theme = config.theme.tableRowDiff;
|
|
2316
|
+
if (typeof theme === "string" && theme) element.classList.add(theme);
|
|
2317
|
+
this.syncDOMState(element);
|
|
2318
|
+
return element;
|
|
2319
|
+
}
|
|
2320
|
+
exportJSON() {
|
|
2321
|
+
return {
|
|
2322
|
+
...super.exportJSON(),
|
|
2323
|
+
changeId: this.__changeId,
|
|
2324
|
+
diffType: this.__diffType,
|
|
2325
|
+
type: TableRowDiffNode.getType(),
|
|
2326
|
+
version: 1
|
|
2327
|
+
};
|
|
2328
|
+
}
|
|
2329
|
+
getChangeId() {
|
|
2330
|
+
return this.getLatest().__changeId;
|
|
2331
|
+
}
|
|
2332
|
+
getDiffType() {
|
|
2333
|
+
return this.getLatest().__diffType;
|
|
2334
|
+
}
|
|
2335
|
+
setChangeId(changeId) {
|
|
2336
|
+
this.getWritable().__changeId = changeId;
|
|
2337
|
+
return this;
|
|
2338
|
+
}
|
|
2339
|
+
setDiffType(diffType) {
|
|
2340
|
+
this.getWritable().__diffType = diffType;
|
|
2341
|
+
return this;
|
|
2342
|
+
}
|
|
2343
|
+
updateDOM(prevNode) {
|
|
2344
|
+
return super.updateDOM(prevNode) || prevNode.__diffType !== this.__diffType || prevNode.__changeId !== this.__changeId;
|
|
2345
|
+
}
|
|
2346
|
+
updateFromJSON(serializedNode) {
|
|
2347
|
+
return super.updateFromJSON(serializedNode).setDiffType(serializedNode.diffType).setChangeId(serializedNode.changeId);
|
|
2348
|
+
}
|
|
2349
|
+
syncDOMState(element) {
|
|
2350
|
+
element.dataset.diffType = this.__diffType;
|
|
2351
|
+
if (this.__changeId) element.dataset.diffChangeId = this.__changeId;
|
|
2352
|
+
else delete element.dataset.diffChangeId;
|
|
2353
|
+
element.contentEditable = "false";
|
|
2354
|
+
}
|
|
2355
|
+
};
|
|
2356
|
+
function $createTableRowDiffNode(diffType, changeId, height) {
|
|
2357
|
+
return $applyNodeReplacement(new TableRowDiffNode(diffType, changeId, height));
|
|
2358
|
+
}
|
|
2359
|
+
function $isTableRowDiffNode(node) {
|
|
2360
|
+
return node instanceof TableRowDiffNode;
|
|
2361
|
+
}
|
|
2362
|
+
//#endregion
|
|
2363
|
+
//#region src/plugins/litexml/table-row-diff.ts
|
|
2364
|
+
function $areTableRowStructuresCompatible(previousRow, nextRow) {
|
|
2365
|
+
const previousCells = previousRow.getChildren();
|
|
2366
|
+
const nextCells = nextRow.getChildren();
|
|
2367
|
+
if (previousCells.length !== nextCells.length || !previousCells.every($isTableCellNode) || !nextCells.every($isTableCellNode)) return false;
|
|
2368
|
+
return previousCells.every((previousCell, index) => {
|
|
2369
|
+
const nextCell = nextCells[index];
|
|
2370
|
+
return $isTableCellNode(nextCell) && previousCell.getColSpan() === nextCell.getColSpan() && previousCell.getRowSpan() === nextCell.getRowSpan();
|
|
2371
|
+
});
|
|
2372
|
+
}
|
|
2373
|
+
function $createTableRowDiffFromRow(editor, row, diffType, changeId) {
|
|
2374
|
+
const source = $cloneNode(row, editor);
|
|
2375
|
+
const diffRow = $createTableRowDiffNode(diffType, changeId, row.getHeight());
|
|
2376
|
+
diffRow.append(...source.getChildren());
|
|
2377
|
+
return diffRow;
|
|
2378
|
+
}
|
|
2379
|
+
function $createPlainTableRowFromDiff(editor, row) {
|
|
2380
|
+
const plainRow = $createTableRowNode(row.getHeight());
|
|
2381
|
+
plainRow.append(...row.getChildren().map((child) => $cloneNode(child, editor)));
|
|
2382
|
+
return plainRow;
|
|
2383
|
+
}
|
|
2384
|
+
function $getTableRowDiffPair(node) {
|
|
2385
|
+
const changeId = node.getChangeId();
|
|
2386
|
+
if (!changeId) return null;
|
|
2387
|
+
const matchingRows = $nodesOfType(TableRowDiffNode).filter((candidate) => candidate.getChangeId() === changeId);
|
|
2388
|
+
if (matchingRows.length !== 2) return null;
|
|
2389
|
+
const remove = matchingRows.find((candidate) => candidate.getDiffType() === "remove");
|
|
2390
|
+
const add = matchingRows.find((candidate) => candidate.getDiffType() === "add");
|
|
2391
|
+
if (!remove || !add) return null;
|
|
2392
|
+
const parent = remove.getParent();
|
|
2393
|
+
if (!$isTableNode(parent) || add.getParent() !== parent || remove.getNextSibling() !== add || !$areTableRowStructuresCompatible(remove, add)) return null;
|
|
2394
|
+
return {
|
|
2395
|
+
add,
|
|
2396
|
+
remove
|
|
2397
|
+
};
|
|
2398
|
+
}
|
|
2399
|
+
function $normalizeTableRowDiffPairs() {
|
|
2400
|
+
const rows = $nodesOfType(TableRowDiffNode);
|
|
2401
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2402
|
+
rows.forEach((row) => {
|
|
2403
|
+
const changeId = row.getChangeId();
|
|
2404
|
+
if (!changeId) return;
|
|
2405
|
+
const group = groups.get(changeId) || [];
|
|
2406
|
+
group.push(row);
|
|
2407
|
+
groups.set(changeId, group);
|
|
2408
|
+
});
|
|
2409
|
+
let changed = false;
|
|
2410
|
+
groups.forEach((group) => {
|
|
2411
|
+
if (group.length === 2 ? $getTableRowDiffPair(group[0]) : null) return;
|
|
2412
|
+
group.forEach((row) => row.setChangeId());
|
|
2413
|
+
changed = true;
|
|
2414
|
+
});
|
|
2415
|
+
return changed;
|
|
2416
|
+
}
|
|
2417
|
+
function registerTableRowDiffNormalization(editor) {
|
|
2418
|
+
let destroyed = false;
|
|
2419
|
+
let scheduled = false;
|
|
2420
|
+
const unregister = editor.registerUpdateListener(() => {
|
|
2421
|
+
if (scheduled || destroyed) return;
|
|
2422
|
+
scheduled = true;
|
|
2423
|
+
queueMicrotask(() => {
|
|
2424
|
+
scheduled = false;
|
|
2425
|
+
if (destroyed) return;
|
|
2426
|
+
if (!editor.getEditorState().read(() => {
|
|
2427
|
+
return $nodesOfType(TableRowDiffNode).some((row) => row.getChangeId() && !$getTableRowDiffPair(row));
|
|
2428
|
+
})) return;
|
|
2429
|
+
editor.update(() => {
|
|
2430
|
+
$normalizeTableRowDiffPairs();
|
|
2431
|
+
});
|
|
2432
|
+
});
|
|
2433
|
+
});
|
|
2434
|
+
return () => {
|
|
2435
|
+
destroyed = true;
|
|
2436
|
+
unregister();
|
|
2437
|
+
};
|
|
2438
|
+
}
|
|
2439
|
+
//#endregion
|
|
1751
2440
|
//#region src/plugins/litexml/command/index.ts
|
|
1752
2441
|
const logger$7 = createDebugLogger("plugin", "litexml");
|
|
1753
2442
|
function toArrayXml(litexml) {
|
|
1754
2443
|
return Array.isArray(litexml) ? litexml : [litexml];
|
|
1755
2444
|
}
|
|
2445
|
+
function hasNewIllegalDiffs(previous, projected) {
|
|
2446
|
+
return findNewIllegalDiffPaths(previous, projected);
|
|
2447
|
+
}
|
|
2448
|
+
function projectOperation(dataSource, document, operation) {
|
|
2449
|
+
try {
|
|
2450
|
+
const projected = projectLiteXmlOperation(document, operation, (xml) => dataSource.readLiteXMLToInode(xml));
|
|
2451
|
+
const newIllegalDiffs = hasNewIllegalDiffs(document, projected);
|
|
2452
|
+
if (newIllegalDiffs.length > 0) {
|
|
2453
|
+
logger$7.warn("⚠️ Skipping operation with illegal nested diff", newIllegalDiffs);
|
|
2454
|
+
return null;
|
|
2455
|
+
}
|
|
2456
|
+
return projected;
|
|
2457
|
+
} catch (error) {
|
|
2458
|
+
logger$7.error("❌ Failed to preflight LiteXML operation:", error);
|
|
2459
|
+
return null;
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
function toProjectionOperation(operation) {
|
|
2463
|
+
if (operation.action === "remove") return {
|
|
2464
|
+
...operation,
|
|
2465
|
+
id: charToId(operation.id)
|
|
2466
|
+
};
|
|
2467
|
+
if (operation.action === "insert") return {
|
|
2468
|
+
...operation,
|
|
2469
|
+
..."beforeId" in operation ? { beforeId: operation.beforeId === "root" ? "root" : charToId(operation.beforeId) } : { afterId: operation.afterId === "root" ? "root" : charToId(operation.afterId) }
|
|
2470
|
+
};
|
|
2471
|
+
return operation;
|
|
2472
|
+
}
|
|
1756
2473
|
function tryParseChild(child, editor) {
|
|
1757
2474
|
try {
|
|
1758
2475
|
const oldNode = $getNodeByKey(child.id);
|
|
@@ -1769,6 +2486,47 @@ function tryParseChild(child, editor) {
|
|
|
1769
2486
|
}
|
|
1770
2487
|
}
|
|
1771
2488
|
function handleReplaceForApplyDelay(oldNode, newNode, modifyBlockNodes, diffNodeMap, editor) {
|
|
2489
|
+
if ($isTableRowNode(oldNode) || $isTableRowNode(newNode)) {
|
|
2490
|
+
if (!$isTableRowNode(oldNode) || !$isTableRowNode(newNode) || !$isTableNode(oldNode.getParent()) || !$areTableRowStructuresCompatible(oldNode, newNode)) {
|
|
2491
|
+
logger$7.error(`❌ Invalid table row modification for row ${oldNode.getKey()}.`);
|
|
2492
|
+
return;
|
|
2493
|
+
}
|
|
2494
|
+
const changeId = `${oldNode.getKey()}:${newNode.getKey()}`;
|
|
2495
|
+
const removeRow = $createTableRowDiffFromRow(editor, oldNode, "remove", changeId);
|
|
2496
|
+
const addRow = $createTableRowDiffFromRow(editor, newNode, "add", changeId);
|
|
2497
|
+
oldNode.replace(removeRow, false);
|
|
2498
|
+
removeRow.insertAfter(addRow);
|
|
2499
|
+
return;
|
|
2500
|
+
}
|
|
2501
|
+
if ($isTableCellNode(oldNode) && $isTableCellNode(newNode)) {
|
|
2502
|
+
const existingDiff = oldNode.getChildren().find($isDiffNode);
|
|
2503
|
+
if (existingDiff) {
|
|
2504
|
+
if ($isTableCellDiffNode(oldNode)) {
|
|
2505
|
+
existingDiff.clear();
|
|
2506
|
+
newNode.getChildren().forEach((child) => {
|
|
2507
|
+
existingDiff.append($cloneNode(child, editor));
|
|
2508
|
+
});
|
|
2509
|
+
return;
|
|
2510
|
+
}
|
|
2511
|
+
const after = existingDiff.getChildren().find((child) => $isDiffContentNode(child) && child.side === "after");
|
|
2512
|
+
if (existingDiff.diffType === "modify" && after && $isDiffContentNode(after)) {
|
|
2513
|
+
after.clear();
|
|
2514
|
+
newNode.getChildren().forEach((child) => {
|
|
2515
|
+
after.append($cloneNode(child, editor));
|
|
2516
|
+
});
|
|
2517
|
+
return;
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
const before = $createDiffContentNode("before");
|
|
2521
|
+
const after = $createDiffContentNode("after");
|
|
2522
|
+
oldNode.getChildren().forEach((child) => before.append($cloneNode(child, editor)));
|
|
2523
|
+
newNode.getChildren().forEach((child) => after.append($cloneNode(child, editor)));
|
|
2524
|
+
const diffNode = $createDiffNode("modify");
|
|
2525
|
+
diffNode.append(before, after);
|
|
2526
|
+
oldNode.clear();
|
|
2527
|
+
oldNode.append(diffNode);
|
|
2528
|
+
return;
|
|
2529
|
+
}
|
|
1772
2530
|
const oldBlock = $closest(oldNode, (node) => node.isInline() === false);
|
|
1773
2531
|
if (!oldBlock) throw new Error("Old block node not found for diffing.");
|
|
1774
2532
|
if ($closest(oldNode, (node) => node.getType() === DiffNode.getType())) {
|
|
@@ -1852,8 +2610,15 @@ function registerLiteXMLCommand(editor, dataSource) {
|
|
|
1852
2610
|
else acc.push(cur);
|
|
1853
2611
|
return acc;
|
|
1854
2612
|
}, []);
|
|
2613
|
+
let projectedDocument = getKernelFromEditor(editor).getDocument("json");
|
|
2614
|
+
const safePayload = resultPayload.filter((item) => {
|
|
2615
|
+
const nextProjection = projectOperation(dataSource, projectedDocument, toProjectionOperation(item));
|
|
2616
|
+
if (!nextProjection) return false;
|
|
2617
|
+
projectedDocument = nextProjection;
|
|
2618
|
+
return true;
|
|
2619
|
+
});
|
|
1855
2620
|
try {
|
|
1856
|
-
|
|
2621
|
+
safePayload.forEach((item) => {
|
|
1857
2622
|
const { action } = item;
|
|
1858
2623
|
switch (action) {
|
|
1859
2624
|
case "modify": {
|
|
@@ -1882,14 +2647,39 @@ function registerLiteXMLCommand(editor, dataSource) {
|
|
|
1882
2647
|
}
|
|
1883
2648
|
}, COMMAND_PRIORITY_EDITOR), editor.registerCommand(LITEXML_APPLY_COMMAND, (payload) => {
|
|
1884
2649
|
const { litexml, delay } = payload;
|
|
1885
|
-
|
|
2650
|
+
const arrayXml = toArrayXml(litexml);
|
|
2651
|
+
if (!delay) {
|
|
2652
|
+
handleModify(editor, dataSource, arrayXml, delay);
|
|
2653
|
+
return false;
|
|
2654
|
+
}
|
|
2655
|
+
const operation = {
|
|
2656
|
+
action: "modify",
|
|
2657
|
+
litexml
|
|
2658
|
+
};
|
|
2659
|
+
if (projectOperation(dataSource, getKernelFromEditor(editor).getDocument("json"), toProjectionOperation(operation))) handleModify(editor, dataSource, arrayXml, delay);
|
|
1886
2660
|
return false;
|
|
1887
2661
|
}, COMMAND_PRIORITY_EDITOR), editor.registerCommand(LITEXML_REMOVE_COMMAND, (payload) => {
|
|
1888
2662
|
const { id, delay } = payload;
|
|
1889
|
-
|
|
2663
|
+
const key = charToId(id);
|
|
2664
|
+
if (!delay) {
|
|
2665
|
+
handleRemove(editor, key, delay);
|
|
2666
|
+
return false;
|
|
2667
|
+
}
|
|
2668
|
+
const operation = {
|
|
2669
|
+
action: "remove",
|
|
2670
|
+
id
|
|
2671
|
+
};
|
|
2672
|
+
if (projectOperation(dataSource, getKernelFromEditor(editor).getDocument("json"), toProjectionOperation(operation))) handleRemove(editor, key, delay);
|
|
1890
2673
|
return false;
|
|
1891
2674
|
}, COMMAND_PRIORITY_EDITOR), editor.registerCommand(LITEXML_INSERT_COMMAND, (payload) => {
|
|
1892
|
-
|
|
2675
|
+
if (!payload.delay) {
|
|
2676
|
+
handleInsert(editor, payload, dataSource);
|
|
2677
|
+
return false;
|
|
2678
|
+
}
|
|
2679
|
+
if (projectOperation(dataSource, getKernelFromEditor(editor).getDocument("json"), toProjectionOperation({
|
|
2680
|
+
action: "insert",
|
|
2681
|
+
...payload
|
|
2682
|
+
}))) handleInsert(editor, payload, dataSource);
|
|
1893
2683
|
return false;
|
|
1894
2684
|
}, COMMAND_PRIORITY_EDITOR));
|
|
1895
2685
|
}
|
|
@@ -1942,9 +2732,32 @@ function handleRemove(editor, key, delay) {
|
|
|
1942
2732
|
const node = $getNodeByKey(key);
|
|
1943
2733
|
if (!node) return;
|
|
1944
2734
|
if (!delay) {
|
|
2735
|
+
if ($isTableCellNode(node)) {
|
|
2736
|
+
const table = $getTableForCell(node);
|
|
2737
|
+
const columnIndex = $getTableCellColumnIndex(node);
|
|
2738
|
+
const span = node.getColSpan();
|
|
2739
|
+
node.remove();
|
|
2740
|
+
if (table && columnIndex >= 0) $shrinkTableWidthsAfterCellRemoval(table, columnIndex, span);
|
|
2741
|
+
return;
|
|
2742
|
+
}
|
|
1945
2743
|
node.remove();
|
|
1946
2744
|
return;
|
|
1947
2745
|
}
|
|
2746
|
+
if ($isTableCellNode(node)) {
|
|
2747
|
+
const table = $getTableForCell(node);
|
|
2748
|
+
const columnIndex = $getTableCellColumnIndex(node);
|
|
2749
|
+
if (!table || columnIndex < 0) {
|
|
2750
|
+
logger$7.error(`❌ Table cell ${node.getKey()} is not attached to a valid table row.`);
|
|
2751
|
+
return;
|
|
2752
|
+
}
|
|
2753
|
+
const changeId = `${table.getKey()}:column:${columnIndex}`;
|
|
2754
|
+
node.replace($createTableCellDiffFromCell(editor, node, "remove", changeId), false);
|
|
2755
|
+
return;
|
|
2756
|
+
}
|
|
2757
|
+
if ($isTableRowNode(node) && $isTableNode(node.getParent())) {
|
|
2758
|
+
node.replace($createTableRowDiffFromRow(editor, node, "remove"), false);
|
|
2759
|
+
return;
|
|
2760
|
+
}
|
|
1948
2761
|
if (node.isInline() === false) {
|
|
1949
2762
|
const originDiffNode = $closest(node, (node) => node.getType() === DiffNode.getType());
|
|
1950
2763
|
if (originDiffNode) {
|
|
@@ -2006,6 +2819,37 @@ function handleInsert(editor, payload, dataSource) {
|
|
|
2006
2819
|
else referenceNode = $getNodeByKey(charToId(payload.afterId));
|
|
2007
2820
|
if (!referenceNode) throw new Error("Reference node not found for insertion.");
|
|
2008
2821
|
const newNodes = inode.root.children.map((child) => $parseSerializedNodeImpl(child, editor));
|
|
2822
|
+
const referencesTableCell = $isTableCellNode(referenceNode);
|
|
2823
|
+
const insertsOnlyTableCells = newNodes.length > 0 && newNodes.every($isTableCellNode);
|
|
2824
|
+
if (referencesTableCell || insertsOnlyTableCells) {
|
|
2825
|
+
if (!referencesTableCell || !insertsOnlyTableCells) {
|
|
2826
|
+
logger$7.error("❌ Table cells can only be inserted next to another cell in the same row.");
|
|
2827
|
+
return;
|
|
2828
|
+
}
|
|
2829
|
+
const cellReference = referenceNode;
|
|
2830
|
+
const table = $getTableForCell(cellReference);
|
|
2831
|
+
const referenceIndex = $getTableCellColumnIndex(cellReference);
|
|
2832
|
+
if (!table || referenceIndex < 0) {
|
|
2833
|
+
logger$7.error("❌ Table cell insertion requires a valid table parent.");
|
|
2834
|
+
return;
|
|
2835
|
+
}
|
|
2836
|
+
const rowWidthBefore = $getLogicalRowWidth(cellReference);
|
|
2837
|
+
const insertionIndex = isBefore ? referenceIndex : referenceIndex + cellReference.getColSpan();
|
|
2838
|
+
$updateTableWidthsForCellInsertion(table, rowWidthBefore, insertionIndex, newNodes.reduce((total, node) => total + ($isTableCellNode(node) ? node.getColSpan() : 0), 0));
|
|
2839
|
+
let spanOffset = 0;
|
|
2840
|
+
const cells = newNodes.map((node) => {
|
|
2841
|
+
const result = delay ? $createTableCellDiffFromCell(editor, node, "add", `${table.getKey()}:column:${insertionIndex + spanOffset}`) : node;
|
|
2842
|
+
spanOffset += node.getColSpan();
|
|
2843
|
+
return result;
|
|
2844
|
+
});
|
|
2845
|
+
if (isBefore) cells.reverse().forEach((cell) => {
|
|
2846
|
+
referenceNode = referenceNode.insertBefore(cell);
|
|
2847
|
+
});
|
|
2848
|
+
else cells.forEach((cell) => {
|
|
2849
|
+
referenceNode = referenceNode.insertAfter(cell);
|
|
2850
|
+
});
|
|
2851
|
+
return;
|
|
2852
|
+
}
|
|
2009
2853
|
if (!delay) {
|
|
2010
2854
|
if (isBefore) newNodes.reverse().forEach((node) => {
|
|
2011
2855
|
referenceNode = referenceNode.insertBefore(node);
|
|
@@ -2015,6 +2859,25 @@ function handleInsert(editor, payload, dataSource) {
|
|
|
2015
2859
|
});
|
|
2016
2860
|
return;
|
|
2017
2861
|
}
|
|
2862
|
+
const referencesTableRow = $isTableRowNode(referenceNode);
|
|
2863
|
+
const insertsOnlyTableRows = newNodes.every($isTableRowNode);
|
|
2864
|
+
if (referencesTableRow || insertsOnlyTableRows) {
|
|
2865
|
+
if (!referencesTableRow || !insertsOnlyTableRows || !$isTableNode(referenceNode.getParent())) {
|
|
2866
|
+
logger$7.error("❌ Table rows can only be inserted next to another row in the same table.");
|
|
2867
|
+
return;
|
|
2868
|
+
}
|
|
2869
|
+
if (isBefore) newNodes.reverse().forEach((node) => {
|
|
2870
|
+
if (!$isTableRowNode(node)) return;
|
|
2871
|
+
const diffRow = $createTableRowDiffFromRow(editor, node, "add");
|
|
2872
|
+
referenceNode = referenceNode.insertBefore(diffRow);
|
|
2873
|
+
});
|
|
2874
|
+
else newNodes.forEach((node) => {
|
|
2875
|
+
if (!$isTableRowNode(node)) return;
|
|
2876
|
+
const diffRow = $createTableRowDiffFromRow(editor, node, "add");
|
|
2877
|
+
referenceNode = referenceNode.insertAfter(diffRow);
|
|
2878
|
+
});
|
|
2879
|
+
return;
|
|
2880
|
+
}
|
|
2018
2881
|
if (isBefore) {
|
|
2019
2882
|
if (referenceNode.isInline() === false) {
|
|
2020
2883
|
const originDiffNode = $closest(referenceNode, (node) => node.getType() === DiffNode.getType());
|
|
@@ -2076,11 +2939,69 @@ function handleInsert(editor, payload, dataSource) {
|
|
|
2076
2939
|
}
|
|
2077
2940
|
//#endregion
|
|
2078
2941
|
//#region src/plugins/litexml/command/diffCommand.ts
|
|
2079
|
-
function
|
|
2942
|
+
function doTableRowAction(editor, node, action) {
|
|
2943
|
+
const pair = $getTableRowDiffPair(node);
|
|
2944
|
+
if (pair) {
|
|
2945
|
+
const survivingRow = action === 1 ? pair.add : pair.remove;
|
|
2946
|
+
const discardedRow = action === 1 ? pair.remove : pair.add;
|
|
2947
|
+
const plainRow = $createPlainTableRowFromDiff(editor, survivingRow);
|
|
2948
|
+
discardedRow.remove();
|
|
2949
|
+
survivingRow.replace(plainRow, false);
|
|
2950
|
+
plainRow.selectStart();
|
|
2951
|
+
return;
|
|
2952
|
+
}
|
|
2953
|
+
if (node.getDiffType() === "remove") {
|
|
2954
|
+
if (action === 1) node.remove();
|
|
2955
|
+
else {
|
|
2956
|
+
const plainRow = $createPlainTableRowFromDiff(editor, node);
|
|
2957
|
+
node.replace(plainRow, false);
|
|
2958
|
+
plainRow.selectStart();
|
|
2959
|
+
}
|
|
2960
|
+
return;
|
|
2961
|
+
}
|
|
2962
|
+
if (action === 1) {
|
|
2963
|
+
const plainRow = $createPlainTableRowFromDiff(editor, node);
|
|
2964
|
+
node.replace(plainRow, false);
|
|
2965
|
+
plainRow.selectStart();
|
|
2966
|
+
} else node.remove();
|
|
2967
|
+
}
|
|
2968
|
+
function doTableCellAction(editor, node, action) {
|
|
2969
|
+
const group = $getTableCellDiffGroup(node);
|
|
2970
|
+
const table = $getTableForCell(node);
|
|
2971
|
+
const columnIndex = $getTableCellColumnIndex(node);
|
|
2972
|
+
const span = node.getColSpan();
|
|
2973
|
+
if (node.getDiffType() === "add" && action === 1 || node.getDiffType() === "remove" && action === 0) {
|
|
2974
|
+
group.forEach((cell) => cell.replace($createPlainTableCellFromDiff(editor, cell), false));
|
|
2975
|
+
return;
|
|
2976
|
+
}
|
|
2977
|
+
if (table && columnIndex >= 0) {
|
|
2978
|
+
const widthCount = table.getColWidths()?.length;
|
|
2979
|
+
$removeTableWidthsForCompleteCellGroup(table, group, columnIndex, span);
|
|
2980
|
+
group.forEach((cell) => cell.remove());
|
|
2981
|
+
if (table.getColWidths()?.length === widthCount) $shrinkTableWidthsAfterCellRemoval(table, columnIndex, span);
|
|
2982
|
+
return;
|
|
2983
|
+
}
|
|
2984
|
+
group.forEach((cell) => cell.remove());
|
|
2985
|
+
}
|
|
2986
|
+
function doAction(editor, node, action) {
|
|
2987
|
+
if ($isTableRowDiffNode(node)) {
|
|
2988
|
+
doTableRowAction(editor, node, action);
|
|
2989
|
+
return;
|
|
2990
|
+
}
|
|
2991
|
+
const parent = node.getParent();
|
|
2992
|
+
if ($isTableCellDiffNode(parent)) {
|
|
2993
|
+
doTableCellAction(editor, parent, action);
|
|
2994
|
+
return;
|
|
2995
|
+
}
|
|
2080
2996
|
if (node.diffType === "modify") {
|
|
2081
2997
|
const children = node.getChildren();
|
|
2082
|
-
|
|
2083
|
-
|
|
2998
|
+
const selectedChild = action === 1 ? children[1] : children[0];
|
|
2999
|
+
if ($isDiffContentNode(selectedChild)) {
|
|
3000
|
+
const parent = node.getParentOrThrow();
|
|
3001
|
+
selectedChild.getChildren().forEach((child) => node.insertBefore(child));
|
|
3002
|
+
node.remove();
|
|
3003
|
+
if ($isElementNode(parent)) parent.selectEnd();
|
|
3004
|
+
} else if (selectedChild) node.replace(selectedChild, false).selectEnd();
|
|
2084
3005
|
}
|
|
2085
3006
|
if (node.diffType === "remove") {
|
|
2086
3007
|
if (action === 1) node.remove();
|
|
@@ -2092,7 +3013,10 @@ function doAction(node, action) {
|
|
|
2092
3013
|
if (node.diffType === "add") {
|
|
2093
3014
|
if (action === 1) {
|
|
2094
3015
|
const children = node.getChildren();
|
|
2095
|
-
node.
|
|
3016
|
+
const parent = node.getParentOrThrow();
|
|
3017
|
+
children.forEach((child) => node.insertBefore(child));
|
|
3018
|
+
node.remove();
|
|
3019
|
+
if ($isElementNode(parent)) parent.selectEnd();
|
|
2096
3020
|
} else if (action === 0) node.remove();
|
|
2097
3021
|
}
|
|
2098
3022
|
if (node.diffType === "listItemModify") {
|
|
@@ -2138,23 +3062,37 @@ function doAction(node, action) {
|
|
|
2138
3062
|
function registerLiteXMLDiffCommand(editor) {
|
|
2139
3063
|
return mergeRegister(editor.registerCommand(LITEXML_DIFFNODE_COMMAND, (payload) => {
|
|
2140
3064
|
const { action, nodeKey } = payload;
|
|
2141
|
-
const node = editor.getEditorState().read(() => {
|
|
2142
|
-
return $getNodeByKey(nodeKey);
|
|
2143
|
-
});
|
|
2144
|
-
if (!node) return false;
|
|
2145
3065
|
editor.update(() => {
|
|
2146
|
-
|
|
3066
|
+
const node = $getNodeByKey(nodeKey);
|
|
3067
|
+
if (!(node instanceof DiffNode) && !$isTableRowDiffNode(node)) return;
|
|
3068
|
+
doAction(editor, node, action);
|
|
2147
3069
|
});
|
|
2148
3070
|
return false;
|
|
2149
3071
|
}, COMMAND_PRIORITY_EDITOR), editor.registerCommand(LITEXML_DIFFNODE_ALL_COMMAND, (payload) => {
|
|
2150
3072
|
const { action } = payload;
|
|
2151
|
-
const
|
|
2152
|
-
return Array.from(editor._editorState._nodeMap.values()).filter((
|
|
3073
|
+
const nodeKeys = editor.getEditorState().read(() => {
|
|
3074
|
+
return Array.from(editor._editorState._nodeMap.values()).filter((node) => (node instanceof DiffNode || $isTableRowDiffNode(node)) && !!node.getParent()).map((node) => node.getKey());
|
|
2153
3075
|
});
|
|
2154
|
-
if (!
|
|
3076
|
+
if (!nodeKeys.length) return false;
|
|
2155
3077
|
editor.update(() => {
|
|
2156
|
-
|
|
2157
|
-
|
|
3078
|
+
const handled = /* @__PURE__ */ new Set();
|
|
3079
|
+
nodeKeys.forEach((nodeKey) => {
|
|
3080
|
+
if (handled.has(nodeKey)) return;
|
|
3081
|
+
const node = $getNodeByKey(nodeKey);
|
|
3082
|
+
if (!(node instanceof DiffNode) && !$isTableRowDiffNode(node)) return;
|
|
3083
|
+
const pair = $isTableRowDiffNode(node) ? $getTableRowDiffPair(node) : null;
|
|
3084
|
+
if (pair) {
|
|
3085
|
+
handled.add(pair.remove.getKey());
|
|
3086
|
+
handled.add(pair.add.getKey());
|
|
3087
|
+
} else {
|
|
3088
|
+
const parent = node instanceof DiffNode ? node.getParent() : null;
|
|
3089
|
+
if ($isTableCellDiffNode(parent)) $getTableCellDiffGroup(parent).forEach((cell) => {
|
|
3090
|
+
const diff = cell.getFirstChild();
|
|
3091
|
+
if (diff instanceof DiffNode) handled.add(diff.getKey());
|
|
3092
|
+
});
|
|
3093
|
+
handled.add(nodeKey);
|
|
3094
|
+
}
|
|
3095
|
+
doAction(editor, node, action);
|
|
2158
3096
|
});
|
|
2159
3097
|
});
|
|
2160
3098
|
return false;
|
|
@@ -2587,8 +3525,29 @@ function convertMdastToLexical(node, index, ctx, markdownReaders = {}, parentTyp
|
|
|
2587
3525
|
return ret;
|
|
2588
3526
|
}
|
|
2589
3527
|
if (child.value.startsWith("<!--") && child.value.endsWith("-->")) return ret;
|
|
2590
|
-
const tag = child.value
|
|
3528
|
+
const tag = getHtmlTagName$1(child.value);
|
|
2591
3529
|
const isEndTag = child.value.startsWith("</");
|
|
3530
|
+
const pairedTag = child.value.match(/^<\s*([a-z0-9-]+)\b[^>]*>([\s\S]*)<\/\s*\1\s*>$/i);
|
|
3531
|
+
if (!isEndTag && pairedTag) {
|
|
3532
|
+
const reader = markdownReaders["html"];
|
|
3533
|
+
const htmlChildren = pairedTag[2] ? [INodeHelper.createTextNode(pairedTag[2])] : [];
|
|
3534
|
+
if (Array.isArray(reader)) for (const element of reader) {
|
|
3535
|
+
const inode = element(child, htmlChildren, index);
|
|
3536
|
+
if (inode) {
|
|
3537
|
+
ret.push(inode);
|
|
3538
|
+
return ret;
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3541
|
+
else if (typeof reader === "function") {
|
|
3542
|
+
const inode = reader(child, htmlChildren, index);
|
|
3543
|
+
if (inode) {
|
|
3544
|
+
ret.push(inode);
|
|
3545
|
+
return ret;
|
|
3546
|
+
}
|
|
3547
|
+
}
|
|
3548
|
+
ret.push(INodeHelper.createTextNode(child.value));
|
|
3549
|
+
return ret;
|
|
3550
|
+
}
|
|
2592
3551
|
if (selfClosingHtmlTags.has(tag)) {
|
|
2593
3552
|
const reader = markdownReaders["html"];
|
|
2594
3553
|
if (Array.isArray(reader)) for (const element of reader) {
|
|
@@ -2675,6 +3634,9 @@ function convertMdastToLexical(node, index, ctx, markdownReaders = {}, parentTyp
|
|
|
2675
3634
|
}
|
|
2676
3635
|
}
|
|
2677
3636
|
}
|
|
3637
|
+
function getHtmlTagName$1(value) {
|
|
3638
|
+
return value.match(/^<\/?\s*([a-z0-9-]+)/i)?.[1]?.toLowerCase() || value.replaceAll(/^<\/?|>$/g, "");
|
|
3639
|
+
}
|
|
2678
3640
|
function registerDefaultReaders(markdownReaders) {
|
|
2679
3641
|
if (!markdownReaders["root"]) markdownReaders["root"] = (node, children) => {
|
|
2680
3642
|
return {
|
|
@@ -3552,6 +4514,7 @@ function detectCodeLanguage(code) {
|
|
|
3552
4514
|
//#region src/plugins/markdown/plugin/index.ts
|
|
3553
4515
|
const DEFAULT_PASTE_MARKDOWN_AUTO_CONVERT_THRESHOLD = 5;
|
|
3554
4516
|
const RICH_HTML_SELECTOR = "strong,em,b,i,h1,h2,h3,h4,h5,h6,ul,ol,table,img,blockquote,pre>code,a[href]";
|
|
4517
|
+
const normalizeMarkdownForClipboard = (markdown) => markdown.replace(/\r?\n$/, "");
|
|
3555
4518
|
const MARKDOWN_DETECTION_RULES = [
|
|
3556
4519
|
{
|
|
3557
4520
|
name: "headers",
|
|
@@ -3748,8 +4711,9 @@ const MarkdownPlugin = class extends KernelPlugin {
|
|
|
3748
4711
|
if (!clipboardData) return false;
|
|
3749
4712
|
const selection = $getSelection();
|
|
3750
4713
|
if (!selection || selection.isCollapsed()) return false;
|
|
3751
|
-
const
|
|
3752
|
-
if (!
|
|
4714
|
+
const rawMarkdown = this.markdownDataSource.write(editor, { selection: true });
|
|
4715
|
+
if (!rawMarkdown) return false;
|
|
4716
|
+
const markdown = normalizeMarkdownForClipboard(rawMarkdown);
|
|
3753
4717
|
event.preventDefault();
|
|
3754
4718
|
const data = $getClipboardDataFromSelection(selection);
|
|
3755
4719
|
setLexicalClipboardDataTransfer(clipboardData, data);
|
|
@@ -3835,14 +4799,23 @@ const LitexmlPlugin = class extends KernelPlugin {
|
|
|
3835
4799
|
super();
|
|
3836
4800
|
this.kernel = kernel;
|
|
3837
4801
|
this.config = config;
|
|
3838
|
-
kernel.registerThemes({
|
|
4802
|
+
kernel.registerThemes({
|
|
4803
|
+
diffNode: config?.theme || "editor_diffNode",
|
|
4804
|
+
tableRowDiff: config?.tableRowDiffTheme || "editor_tableRowDiff",
|
|
4805
|
+
tableCellDiff: config?.tableCellDiffTheme || "editor_tableCellDiff"
|
|
4806
|
+
});
|
|
3839
4807
|
const litexmlService = new LitexmlService();
|
|
3840
4808
|
this.service = litexmlService;
|
|
3841
4809
|
kernel.registerService(ILitexmlService, litexmlService);
|
|
3842
4810
|
this.datasource = new LitexmlDataSource("litexml", (serviceId) => {
|
|
3843
4811
|
return kernel.requireService(serviceId);
|
|
3844
4812
|
}, litexmlService);
|
|
3845
|
-
kernel.registerNodes([
|
|
4813
|
+
kernel.registerNodes([
|
|
4814
|
+
DiffNode,
|
|
4815
|
+
DiffContentNode,
|
|
4816
|
+
TableRowDiffNode,
|
|
4817
|
+
TableCellDiffNode
|
|
4818
|
+
]);
|
|
3846
4819
|
kernel.registerDecorator(DiffNode.getType(), {
|
|
3847
4820
|
queryDOM: (el) => el.querySelector(".toolbar"),
|
|
3848
4821
|
render: (node, editor) => {
|
|
@@ -3854,6 +4827,8 @@ const LitexmlPlugin = class extends KernelPlugin {
|
|
|
3854
4827
|
onInit(editor) {
|
|
3855
4828
|
this.register(registerLiteXMLCommand(editor, this.datasource));
|
|
3856
4829
|
this.register(registerLiteXMLDiffCommand(editor));
|
|
4830
|
+
this.register(registerLegacyTableCellDiffNormalization(editor));
|
|
4831
|
+
this.register(registerTableRowDiffNormalization(editor));
|
|
3857
4832
|
this.register(editor.registerNodeTransform(DiffNode, (node) => {
|
|
3858
4833
|
if (node.diffType === "modify" && node.getChildrenSize() === 1) node.setDiffType("remove");
|
|
3859
4834
|
}));
|
|
@@ -3878,11 +4853,14 @@ const LitexmlPlugin = class extends KernelPlugin {
|
|
|
3878
4853
|
const diffNode = node;
|
|
3879
4854
|
const lines = [];
|
|
3880
4855
|
switch (diffNode.diffType) {
|
|
3881
|
-
case "modify":
|
|
3882
|
-
|
|
4856
|
+
case "modify": {
|
|
4857
|
+
const after = diffNode.getChildAtIndex(1);
|
|
4858
|
+
if ($isDiffContentNode(after)) after.getChildren().forEach((child) => nodeToXML(child, lines, indent));
|
|
4859
|
+
else nodeToXML(after, lines, indent);
|
|
3883
4860
|
break;
|
|
4861
|
+
}
|
|
3884
4862
|
case "add":
|
|
3885
|
-
|
|
4863
|
+
diffNode.getChildren().forEach((child) => nodeToXML(child, lines, indent));
|
|
3886
4864
|
break;
|
|
3887
4865
|
case "remove": break;
|
|
3888
4866
|
case "listItemModify": diffNode.getChildAtIndex(1).getChildren().forEach((child) => {
|
|
@@ -3891,16 +4869,49 @@ const LitexmlPlugin = class extends KernelPlugin {
|
|
|
3891
4869
|
}
|
|
3892
4870
|
return { lines };
|
|
3893
4871
|
});
|
|
4872
|
+
this.service.registerXMLWriter(TableRowDiffNode.getType(), (node, ctx) => {
|
|
4873
|
+
if (!$isTableRowDiffNode(node)) return false;
|
|
4874
|
+
if (node.getDiffType() === "remove") return { lines: [] };
|
|
4875
|
+
return ctx.createXmlNode("tr", {});
|
|
4876
|
+
});
|
|
4877
|
+
this.service.registerXMLWriter(TableCellDiffNode.getType(), (node, ctx) => {
|
|
4878
|
+
if (!$isTableCellDiffNode(node)) return false;
|
|
4879
|
+
if (node.getDiffType() === "remove") return { lines: [] };
|
|
4880
|
+
const attributes = {};
|
|
4881
|
+
if (node.getColSpan() > 1) attributes.colSpan = String(node.getColSpan());
|
|
4882
|
+
if (node.getRowSpan() > 1) attributes.rowSpan = String(node.getRowSpan());
|
|
4883
|
+
if (node.getBackgroundColor()) attributes.backgroundColor = node.getBackgroundColor();
|
|
4884
|
+
return ctx.createXmlNode("td", attributes);
|
|
4885
|
+
});
|
|
3894
4886
|
}
|
|
3895
4887
|
registerMarkdown() {
|
|
3896
|
-
this.kernel.requireService(IMarkdownShortCutService)
|
|
4888
|
+
const markdownService = this.kernel.requireService(IMarkdownShortCutService);
|
|
4889
|
+
markdownService?.registerMarkdownWriter(TableCellDiffNode.getType(), (ctx, node) => {
|
|
4890
|
+
if (!$isTableCellDiffNode(node)) return false;
|
|
4891
|
+
if (node.getDiffType() === "remove") return true;
|
|
4892
|
+
if (!node.getNextSibling()) ctx.wrap("|", "|");
|
|
4893
|
+
else ctx.wrap("|", "");
|
|
4894
|
+
return true;
|
|
4895
|
+
});
|
|
4896
|
+
markdownService?.registerMarkdownWriter(TableRowDiffNode.getType(), (ctx, node) => {
|
|
4897
|
+
if (!$isTableRowDiffNode(node)) return false;
|
|
4898
|
+
if (node.getDiffType() === "remove") return true;
|
|
4899
|
+
const parent = node.getParent();
|
|
4900
|
+
if (!$isTableNode(parent)) return true;
|
|
4901
|
+
if (node.getPreviousSiblings().some((sibling) => !$isTableRowDiffNode(sibling) || sibling.getDiffType() !== "remove")) ctx.wrap("", "\n");
|
|
4902
|
+
else ctx.wrap("", `\n${parent.getColWidths()?.map(() => "|:--").join("")}|\n`);
|
|
4903
|
+
});
|
|
4904
|
+
markdownService?.registerMarkdownWriter(DiffNode.getType(), (ctx, node) => {
|
|
3897
4905
|
const diffNode = node;
|
|
3898
4906
|
switch (diffNode.diffType) {
|
|
3899
|
-
case "modify":
|
|
3900
|
-
|
|
4907
|
+
case "modify": {
|
|
4908
|
+
const after = diffNode.getChildAtIndex(1);
|
|
4909
|
+
if ($isDiffContentNode(after)) after.getChildren().forEach((child) => ctx.processChild(ctx, child));
|
|
4910
|
+
else ctx.processChild(ctx, after);
|
|
3901
4911
|
break;
|
|
4912
|
+
}
|
|
3902
4913
|
case "add":
|
|
3903
|
-
ctx.processChild(ctx,
|
|
4914
|
+
diffNode.getChildren().forEach((child) => ctx.processChild(ctx, child));
|
|
3904
4915
|
break;
|
|
3905
4916
|
case "remove": break;
|
|
3906
4917
|
case "listItemModify": diffNode.getChildAtIndex(1).getChildren().forEach((child) => {
|
|
@@ -4414,6 +5425,84 @@ function runPasteHandlers(ctx, handlers) {
|
|
|
4414
5425
|
return false;
|
|
4415
5426
|
}
|
|
4416
5427
|
//#endregion
|
|
5428
|
+
//#region src/plugins/common/plugin/progressive-select-all.ts
|
|
5429
|
+
const COLLAPSIBLE_NODE_TYPE = "collapsible";
|
|
5430
|
+
function registerProgressiveSelectAll(editor) {
|
|
5431
|
+
return editor.registerCommand(SELECT_ALL_COMMAND, () => {
|
|
5432
|
+
const selection = $getSelection();
|
|
5433
|
+
if ($isTableSelection(selection)) return $expandTableSelection(selection);
|
|
5434
|
+
if (!$isRangeSelection(selection)) return false;
|
|
5435
|
+
const anchorCell = $findNearestNode(selection.anchor.getNode(), $isTableCellNode);
|
|
5436
|
+
const focusCell = $findNearestNode(selection.focus.getNode(), $isTableCellNode);
|
|
5437
|
+
if (anchorCell && focusCell && anchorCell.is(focusCell)) {
|
|
5438
|
+
const table = $findNearestNode(anchorCell, $isTableNode);
|
|
5439
|
+
if (table) {
|
|
5440
|
+
const tableSelection = $createTableSelection();
|
|
5441
|
+
tableSelection.set(table.getKey(), anchorCell.getKey(), anchorCell.getKey());
|
|
5442
|
+
$setSelection(tableSelection);
|
|
5443
|
+
return true;
|
|
5444
|
+
}
|
|
5445
|
+
}
|
|
5446
|
+
const anchorCollapsible = $findCollapsibleAncestor(selection.anchor.getNode());
|
|
5447
|
+
const focusCollapsible = $findCollapsibleAncestor(selection.focus.getNode());
|
|
5448
|
+
if (!anchorCollapsible || !focusCollapsible || !anchorCollapsible.is(focusCollapsible)) return false;
|
|
5449
|
+
if ($isEntireElementSelected(selection, anchorCollapsible)) $selectDocument();
|
|
5450
|
+
else anchorCollapsible.select(0, anchorCollapsible.getChildrenSize());
|
|
5451
|
+
return true;
|
|
5452
|
+
}, COMMAND_PRIORITY_HIGH);
|
|
5453
|
+
}
|
|
5454
|
+
function $expandTableSelection(selection) {
|
|
5455
|
+
const table = $getNodeByKey(selection.tableKey);
|
|
5456
|
+
if (!$isTableNode(table)) return false;
|
|
5457
|
+
const cells = $getTableCells(table);
|
|
5458
|
+
if (!cells) return false;
|
|
5459
|
+
const selectedCellKeys = new Set(selection.getNodes().filter($isTableCellNode).map((cell) => cell.getKey()));
|
|
5460
|
+
if (cells.keys.every((key) => selectedCellKeys.has(key))) {
|
|
5461
|
+
$selectDocument();
|
|
5462
|
+
return true;
|
|
5463
|
+
}
|
|
5464
|
+
const tableSelection = $createTableSelection();
|
|
5465
|
+
tableSelection.set(table.getKey(), cells.first.getKey(), cells.last.getKey());
|
|
5466
|
+
$setSelection(tableSelection);
|
|
5467
|
+
return true;
|
|
5468
|
+
}
|
|
5469
|
+
function $getTableCells(table) {
|
|
5470
|
+
if (!$isTableNode(table)) return null;
|
|
5471
|
+
const [tableMap] = $computeTableMapSkipCellCheck(table, null, null);
|
|
5472
|
+
const entries = tableMap.flat().filter(Boolean);
|
|
5473
|
+
const first = entries[0]?.cell;
|
|
5474
|
+
const last = entries.at(-1)?.cell;
|
|
5475
|
+
if (!first || !last) return null;
|
|
5476
|
+
return {
|
|
5477
|
+
first,
|
|
5478
|
+
keys: Array.from(new Set(entries.map(({ cell }) => cell.getKey()))),
|
|
5479
|
+
last
|
|
5480
|
+
};
|
|
5481
|
+
}
|
|
5482
|
+
function $findNearestNode(node, predicate) {
|
|
5483
|
+
let current = node;
|
|
5484
|
+
while (current) {
|
|
5485
|
+
if (predicate(current)) return current;
|
|
5486
|
+
current = current.getParent();
|
|
5487
|
+
}
|
|
5488
|
+
return null;
|
|
5489
|
+
}
|
|
5490
|
+
function $findCollapsibleAncestor(node) {
|
|
5491
|
+
return $findNearestNode(node, (candidate) => $isElementNode(candidate) && candidate.getType() === COLLAPSIBLE_NODE_TYPE);
|
|
5492
|
+
}
|
|
5493
|
+
function $isEntireElementSelected(selection, element) {
|
|
5494
|
+
const startOffset = 0;
|
|
5495
|
+
const endOffset = element.getChildrenSize();
|
|
5496
|
+
return $isPointAt(selection.anchor, element, startOffset) && $isPointAt(selection.focus, element, endOffset) || $isPointAt(selection.focus, element, startOffset) && $isPointAt(selection.anchor, element, endOffset);
|
|
5497
|
+
}
|
|
5498
|
+
function $isPointAt(point, element, offset) {
|
|
5499
|
+
return point.key === element.getKey() && point.offset === offset && point.type === "element";
|
|
5500
|
+
}
|
|
5501
|
+
function $selectDocument() {
|
|
5502
|
+
const root = $getRoot();
|
|
5503
|
+
root.select(0, root.getChildrenSize());
|
|
5504
|
+
}
|
|
5505
|
+
//#endregion
|
|
4417
5506
|
//#region src/plugins/common/plugin/register.ts
|
|
4418
5507
|
const logger$3 = createDebugLogger("plugin", "common");
|
|
4419
5508
|
function resolveElement(element, isBackward, focusOffset) {
|
|
@@ -4429,7 +5518,9 @@ function resolveElement(element, isBackward, focusOffset) {
|
|
|
4429
5518
|
block = parent;
|
|
4430
5519
|
}
|
|
4431
5520
|
}
|
|
4432
|
-
|
|
5521
|
+
const childIndex = isBackward ? offset - 1 : offset;
|
|
5522
|
+
if (childIndex < 0 || childIndex >= block.getChildrenSize()) return null;
|
|
5523
|
+
return block.getChildAtIndex(childIndex);
|
|
4433
5524
|
}
|
|
4434
5525
|
function isCodeNodeLastLine(focusNode) {
|
|
4435
5526
|
if (!$isCodeHighlightNode(focusNode)) return false;
|
|
@@ -4474,7 +5565,55 @@ function $isSelectionAtEndOfRoot(selection) {
|
|
|
4474
5565
|
const focus = selection.focus;
|
|
4475
5566
|
return focus.key === "root" && focus.offset === $getRoot().getChildrenSize();
|
|
4476
5567
|
}
|
|
4477
|
-
function
|
|
5568
|
+
function $isSelectionInList(selection) {
|
|
5569
|
+
return Boolean($closest(selection.focus.getNode(), (node) => $isListNode(node) || $isListItemNode(node)));
|
|
5570
|
+
}
|
|
5571
|
+
function $getLeadingQuoteNode(selection) {
|
|
5572
|
+
const anchor = selection.anchor;
|
|
5573
|
+
if (anchor.offset !== 0) return false;
|
|
5574
|
+
let node = anchor.getNode();
|
|
5575
|
+
let current = node;
|
|
5576
|
+
let quoteNode = null;
|
|
5577
|
+
while (current) {
|
|
5578
|
+
if ($isQuoteNode(current)) {
|
|
5579
|
+
quoteNode = current;
|
|
5580
|
+
break;
|
|
5581
|
+
}
|
|
5582
|
+
current = current.getParent();
|
|
5583
|
+
}
|
|
5584
|
+
if (!quoteNode) return false;
|
|
5585
|
+
while (node !== quoteNode) {
|
|
5586
|
+
if (node.getIndexWithinParent() !== 0) return false;
|
|
5587
|
+
const parent = node.getParent();
|
|
5588
|
+
if (!parent) return false;
|
|
5589
|
+
node = parent;
|
|
5590
|
+
}
|
|
5591
|
+
return quoteNode;
|
|
5592
|
+
}
|
|
5593
|
+
function $unwrapQuoteNode(quoteNode) {
|
|
5594
|
+
const children = quoteNode.getChildren();
|
|
5595
|
+
if (children.length === 0) {
|
|
5596
|
+
const paragraphNode = $createParagraphNode();
|
|
5597
|
+
quoteNode.replace(paragraphNode);
|
|
5598
|
+
paragraphNode.select(0, 0);
|
|
5599
|
+
return;
|
|
5600
|
+
}
|
|
5601
|
+
let firstNode = null;
|
|
5602
|
+
for (const child of children) {
|
|
5603
|
+
if ($isElementNode(child)) {
|
|
5604
|
+
quoteNode.insertBefore(child);
|
|
5605
|
+
firstNode ||= child;
|
|
5606
|
+
continue;
|
|
5607
|
+
}
|
|
5608
|
+
const paragraphNode = $createParagraphNode();
|
|
5609
|
+
paragraphNode.append(child);
|
|
5610
|
+
quoteNode.insertBefore(paragraphNode);
|
|
5611
|
+
firstNode ||= paragraphNode;
|
|
5612
|
+
}
|
|
5613
|
+
quoteNode.remove();
|
|
5614
|
+
if ($isElementNode(firstNode)) firstNode.select(0, 0);
|
|
5615
|
+
}
|
|
5616
|
+
function registerBlockBackspace(editor) {
|
|
4478
5617
|
return editor.registerCommand(KEY_BACKSPACE_COMMAND, (payload) => {
|
|
4479
5618
|
const headingNode = editor.getEditorState().read(() => {
|
|
4480
5619
|
const selection = $getSelection();
|
|
@@ -4502,6 +5641,22 @@ function registerHeaderBackspace(editor) {
|
|
|
4502
5641
|
});
|
|
4503
5642
|
return true;
|
|
4504
5643
|
}
|
|
5644
|
+
if (editor.getEditorState().read(() => {
|
|
5645
|
+
const selection = $getSelection();
|
|
5646
|
+
if (!$isRangeSelection(selection) || !selection.isCollapsed()) return false;
|
|
5647
|
+
return $getLeadingQuoteNode(selection);
|
|
5648
|
+
})) {
|
|
5649
|
+
payload.stopImmediatePropagation();
|
|
5650
|
+
payload.preventDefault();
|
|
5651
|
+
payload.stopPropagation();
|
|
5652
|
+
editor.update(() => {
|
|
5653
|
+
const selection = $getSelection();
|
|
5654
|
+
if (!$isRangeSelection(selection) || !selection.isCollapsed()) return;
|
|
5655
|
+
const leadingQuoteNode = $getLeadingQuoteNode(selection);
|
|
5656
|
+
if (leadingQuoteNode) $unwrapQuoteNode(leadingQuoteNode);
|
|
5657
|
+
});
|
|
5658
|
+
return true;
|
|
5659
|
+
}
|
|
4505
5660
|
return false;
|
|
4506
5661
|
}, COMMAND_PRIORITY_NORMAL);
|
|
4507
5662
|
}
|
|
@@ -4556,6 +5711,7 @@ function registerRichKeydown(editor, kernel, options) {
|
|
|
4556
5711
|
return true;
|
|
4557
5712
|
}
|
|
4558
5713
|
} else if ($isRangeSelection(selection)) {
|
|
5714
|
+
if ($isSelectionInList(selection)) return false;
|
|
4559
5715
|
const possibleNode = $getAdjacentNode(selection.focus, true);
|
|
4560
5716
|
const upblock = possibleNode || $getDownUpNode(selection.focus, true);
|
|
4561
5717
|
if (!event.shiftKey && $isDecoratorNode(possibleNode)) {
|
|
@@ -4625,6 +5781,7 @@ function registerRichKeydown(editor, kernel, options) {
|
|
|
4625
5781
|
event.preventDefault();
|
|
4626
5782
|
return true;
|
|
4627
5783
|
}
|
|
5784
|
+
if ($isSelectionInList(selection)) return false;
|
|
4628
5785
|
const possibleNode = $getAdjacentNode(selection.focus, false);
|
|
4629
5786
|
const upblock = possibleNode || $getDownUpNode(selection.focus, false);
|
|
4630
5787
|
if (!event.shiftKey && $isDecoratorNode(possibleNode)) {
|
|
@@ -4919,7 +6076,7 @@ const CommonPlugin = class extends KernelPlugin {
|
|
|
4919
6076
|
if (this.config?.pasteAsPlainText) return runPasteHandlers(ctx, [handleFilePaste, handlePlainTextPaste]);
|
|
4920
6077
|
return false;
|
|
4921
6078
|
}, COMMAND_PRIORITY_CRITICAL));
|
|
4922
|
-
this.registerClears(registerRichText(editor), CAN_USE_DOM ? registerDragonSupport(editor) : noop, registerHistory(editor, this.kernel.getHistoryState(), 300),
|
|
6079
|
+
this.registerClears(registerRichText(editor), registerProgressiveSelectAll(editor), CAN_USE_DOM ? registerDragonSupport(editor) : noop, registerHistory(editor, this.kernel.getHistoryState(), 300), registerBlockBackspace(editor), registerRichKeydown(editor, this.kernel, { enableHotkey: this.config?.enableHotkey }), registerCommands(editor), registerBreakLineClick(editor), registerCursorNode(editor), registerLastElement(editor), editor.registerCommand(INSERT_LINE_BREAK_COMMAND, () => {
|
|
4923
6080
|
const selection = $getSelection();
|
|
4924
6081
|
if (!$isRangeSelection(selection)) return false;
|
|
4925
6082
|
const { inCell, inTable } = $isCursorInTable(selection);
|
|
@@ -5793,6 +6950,62 @@ function modeMatch(mode = "") {
|
|
|
5793
6950
|
return LANGUAGES.find((m) => m.value === mode || m.ext?.includes(mode))?.value || "plain";
|
|
5794
6951
|
}
|
|
5795
6952
|
//#endregion
|
|
6953
|
+
//#region src/plugins/codemirror-block/service/index.ts
|
|
6954
|
+
const ICodemirrorEditLockService = genServiceId("CodemirrorEditLockService");
|
|
6955
|
+
var CodemirrorEditLockService = class {
|
|
6956
|
+
constructor() {
|
|
6957
|
+
this.providerUnsubscribers = /* @__PURE__ */ new Map();
|
|
6958
|
+
this.providers = /* @__PURE__ */ new Set();
|
|
6959
|
+
this.subscribers = /* @__PURE__ */ new Set();
|
|
6960
|
+
}
|
|
6961
|
+
acquireLock(key, label) {
|
|
6962
|
+
if (this.getRemoteLockOwner(key)) return false;
|
|
6963
|
+
const acquiredProviders = [];
|
|
6964
|
+
for (const provider of this.providers) {
|
|
6965
|
+
if (!provider.acquireLock(key, label)) {
|
|
6966
|
+
acquiredProviders.forEach((acquiredProvider) => acquiredProvider.releaseLock(key));
|
|
6967
|
+
this.notify();
|
|
6968
|
+
return false;
|
|
6969
|
+
}
|
|
6970
|
+
acquiredProviders.push(provider);
|
|
6971
|
+
}
|
|
6972
|
+
this.notify();
|
|
6973
|
+
return true;
|
|
6974
|
+
}
|
|
6975
|
+
getRemoteLockOwner(key) {
|
|
6976
|
+
for (const provider of this.providers) {
|
|
6977
|
+
const owner = provider.getRemoteLockOwner(key);
|
|
6978
|
+
if (owner) return owner;
|
|
6979
|
+
}
|
|
6980
|
+
return null;
|
|
6981
|
+
}
|
|
6982
|
+
registerProvider(provider) {
|
|
6983
|
+
this.providers.add(provider);
|
|
6984
|
+
this.providerUnsubscribers.set(provider, provider.subscribe(() => this.notify()));
|
|
6985
|
+
this.notify();
|
|
6986
|
+
return () => {
|
|
6987
|
+
this.providerUnsubscribers.get(provider)?.();
|
|
6988
|
+
this.providerUnsubscribers.delete(provider);
|
|
6989
|
+
this.providers.delete(provider);
|
|
6990
|
+
this.notify();
|
|
6991
|
+
};
|
|
6992
|
+
}
|
|
6993
|
+
releaseLock(key) {
|
|
6994
|
+
this.providers.forEach((provider) => provider.releaseLock(key));
|
|
6995
|
+
this.notify();
|
|
6996
|
+
}
|
|
6997
|
+
subscribe(listener) {
|
|
6998
|
+
this.subscribers.add(listener);
|
|
6999
|
+
listener();
|
|
7000
|
+
return () => {
|
|
7001
|
+
this.subscribers.delete(listener);
|
|
7002
|
+
};
|
|
7003
|
+
}
|
|
7004
|
+
notify() {
|
|
7005
|
+
this.subscribers.forEach((listener) => listener());
|
|
7006
|
+
}
|
|
7007
|
+
};
|
|
7008
|
+
//#endregion
|
|
5796
7009
|
//#region src/plugins/codemirror-block/plugin/index.ts
|
|
5797
7010
|
const CodemirrorPlugin = class extends KernelPlugin {
|
|
5798
7011
|
static {
|
|
@@ -5803,6 +7016,7 @@ const CodemirrorPlugin = class extends KernelPlugin {
|
|
|
5803
7016
|
this.kernel = kernel;
|
|
5804
7017
|
kernel.registerNodes([CodeMirrorNode]);
|
|
5805
7018
|
kernel.registerThemes({ hr: config?.theme || "" });
|
|
7019
|
+
kernel.registerServiceHotReload(ICodemirrorEditLockService, new CodemirrorEditLockService());
|
|
5806
7020
|
this.registerDecorator(kernel, "codemirror", (node, editor) => {
|
|
5807
7021
|
return config?.decorator ? config.decorator(node, editor) : null;
|
|
5808
7022
|
});
|
|
@@ -6762,6 +7976,263 @@ const ImagePlugin = class extends KernelPlugin {
|
|
|
6762
7976
|
}
|
|
6763
7977
|
};
|
|
6764
7978
|
//#endregion
|
|
7979
|
+
//#region src/plugins/link/service/i-link-service.ts
|
|
7980
|
+
const ILinkService = genServiceId("LinkService");
|
|
7981
|
+
const DEFAULT_LABELS = {
|
|
7982
|
+
convertToBlockCard: "Convert to block card",
|
|
7983
|
+
convertToCard: "Convert to card",
|
|
7984
|
+
convertToIframe: "Convert to iframe",
|
|
7985
|
+
convertToLink: "Convert to link",
|
|
7986
|
+
convertToSchema: "Convert to schema"
|
|
7987
|
+
};
|
|
7988
|
+
var LinkProtocolPolicy = class {
|
|
7989
|
+
constructor() {
|
|
7990
|
+
this.allowedProtocols = /* @__PURE__ */ new Set([
|
|
7991
|
+
"http:",
|
|
7992
|
+
"https:",
|
|
7993
|
+
"mailto:",
|
|
7994
|
+
"sms:",
|
|
7995
|
+
"tel:"
|
|
7996
|
+
]);
|
|
7997
|
+
}
|
|
7998
|
+
getAllowedProtocols() {
|
|
7999
|
+
return this.allowedProtocols;
|
|
8000
|
+
}
|
|
8001
|
+
setAllowedProtocols(protocols) {
|
|
8002
|
+
this.allowedProtocols = new Set(protocols.map(normalizeProtocol));
|
|
8003
|
+
}
|
|
8004
|
+
};
|
|
8005
|
+
var LinkRuleRegistry = class {
|
|
8006
|
+
constructor() {
|
|
8007
|
+
this.embedRules = [];
|
|
8008
|
+
this.schemaRules = [];
|
|
8009
|
+
}
|
|
8010
|
+
getEmbedRule(url, context) {
|
|
8011
|
+
return this.embedRules.find((rule) => rule.match(url, context)) || null;
|
|
8012
|
+
}
|
|
8013
|
+
getSchemaRule(url, context, parseSchemaUrl) {
|
|
8014
|
+
const schema = context.schema === void 0 ? parseSchemaUrl(url) : context.schema;
|
|
8015
|
+
return this.schemaRules.find((rule) => rule.match(url, {
|
|
8016
|
+
...context,
|
|
8017
|
+
schema
|
|
8018
|
+
})) || null;
|
|
8019
|
+
}
|
|
8020
|
+
getSchemaRuleById(schemaType) {
|
|
8021
|
+
return this.schemaRules.find((rule) => rule.id === schemaType) || null;
|
|
8022
|
+
}
|
|
8023
|
+
setEmbedRules(rules = []) {
|
|
8024
|
+
this.embedRules = rules;
|
|
8025
|
+
}
|
|
8026
|
+
setSchemaRules(rules = []) {
|
|
8027
|
+
this.schemaRules = rules;
|
|
8028
|
+
}
|
|
8029
|
+
};
|
|
8030
|
+
var LegacySchemaLinkRegistry = class {
|
|
8031
|
+
constructor() {
|
|
8032
|
+
this.protocols = /* @__PURE__ */ new Set();
|
|
8033
|
+
}
|
|
8034
|
+
hasSchemaLinkRenderer(url) {
|
|
8035
|
+
try {
|
|
8036
|
+
const { protocol } = new URL(url);
|
|
8037
|
+
return this.protocols.has(protocol);
|
|
8038
|
+
} catch {
|
|
8039
|
+
return false;
|
|
8040
|
+
}
|
|
8041
|
+
}
|
|
8042
|
+
setSchemaLinkRenderers(renderers = []) {
|
|
8043
|
+
this.protocols = new Set(renderers.map(({ protocol }) => normalizeProtocol(protocol)));
|
|
8044
|
+
}
|
|
8045
|
+
};
|
|
8046
|
+
var LinkToolbarController = class {
|
|
8047
|
+
constructor() {
|
|
8048
|
+
this.baseEnabled = true;
|
|
8049
|
+
this.labels = DEFAULT_LABELS;
|
|
8050
|
+
this.toolbarActions = [];
|
|
8051
|
+
this.toolbarSuppressions = /* @__PURE__ */ new Set();
|
|
8052
|
+
}
|
|
8053
|
+
get enabled() {
|
|
8054
|
+
return this.baseEnabled && this.toolbarSuppressions.size === 0;
|
|
8055
|
+
}
|
|
8056
|
+
getLabels() {
|
|
8057
|
+
return this.labels;
|
|
8058
|
+
}
|
|
8059
|
+
getToolbarActions(context) {
|
|
8060
|
+
return this.toolbarActions.filter((action) => action.visible?.(context) ?? true);
|
|
8061
|
+
}
|
|
8062
|
+
setLabels(labels) {
|
|
8063
|
+
this.labels = {
|
|
8064
|
+
...DEFAULT_LABELS,
|
|
8065
|
+
...labels
|
|
8066
|
+
};
|
|
8067
|
+
}
|
|
8068
|
+
setToolbarActions(actions = []) {
|
|
8069
|
+
this.toolbarActions = actions;
|
|
8070
|
+
}
|
|
8071
|
+
setEnabled(enable) {
|
|
8072
|
+
const previousEnabled = this.enabled;
|
|
8073
|
+
this.baseEnabled = enable;
|
|
8074
|
+
return previousEnabled !== this.enabled;
|
|
8075
|
+
}
|
|
8076
|
+
suppress(reason = "unknown") {
|
|
8077
|
+
const previousEnabled = this.enabled;
|
|
8078
|
+
const token = Symbol(reason);
|
|
8079
|
+
this.toolbarSuppressions.add(token);
|
|
8080
|
+
return {
|
|
8081
|
+
changed: previousEnabled !== this.enabled,
|
|
8082
|
+
token
|
|
8083
|
+
};
|
|
8084
|
+
}
|
|
8085
|
+
restore(token) {
|
|
8086
|
+
const previousEnabled = this.enabled;
|
|
8087
|
+
if (!this.toolbarSuppressions.delete(token)) return false;
|
|
8088
|
+
return previousEnabled !== this.enabled;
|
|
8089
|
+
}
|
|
8090
|
+
};
|
|
8091
|
+
var LinkService = class extends EventEmitter {
|
|
8092
|
+
constructor(..._args) {
|
|
8093
|
+
super(..._args);
|
|
8094
|
+
this.cardMetadataLoadingKeys = /* @__PURE__ */ new Set();
|
|
8095
|
+
this.protocolPolicy = new LinkProtocolPolicy();
|
|
8096
|
+
this.legacySchemaLinkRegistry = new LegacySchemaLinkRegistry();
|
|
8097
|
+
this.ruleRegistry = new LinkRuleRegistry();
|
|
8098
|
+
this.toolbarController = new LinkToolbarController();
|
|
8099
|
+
this.toolbarItems = /* @__PURE__ */ new Map();
|
|
8100
|
+
}
|
|
8101
|
+
get enableLinkToolbar() {
|
|
8102
|
+
return this.toolbarController.enabled;
|
|
8103
|
+
}
|
|
8104
|
+
getAllowedProtocols() {
|
|
8105
|
+
return this.protocolPolicy.getAllowedProtocols();
|
|
8106
|
+
}
|
|
8107
|
+
getEmbedRule(url, context) {
|
|
8108
|
+
return this.ruleRegistry.getEmbedRule(url, context);
|
|
8109
|
+
}
|
|
8110
|
+
getLabels() {
|
|
8111
|
+
return this.toolbarController.getLabels();
|
|
8112
|
+
}
|
|
8113
|
+
hasSchemaLinkRenderer(url) {
|
|
8114
|
+
return this.legacySchemaLinkRegistry.hasSchemaLinkRenderer(url);
|
|
8115
|
+
}
|
|
8116
|
+
isCardMetadataLoading(nodeKey) {
|
|
8117
|
+
return this.cardMetadataLoadingKeys.has(nodeKey);
|
|
8118
|
+
}
|
|
8119
|
+
setCardMetadataLoading(nodeKey, loading) {
|
|
8120
|
+
if (!(loading ? !this.cardMetadataLoadingKeys.has(nodeKey) : this.cardMetadataLoadingKeys.has(nodeKey))) return;
|
|
8121
|
+
if (loading) this.cardMetadataLoadingKeys.add(nodeKey);
|
|
8122
|
+
else this.cardMetadataLoadingKeys.delete(nodeKey);
|
|
8123
|
+
this.emit("cardMetadataChange");
|
|
8124
|
+
}
|
|
8125
|
+
getSchemaRule(url, context) {
|
|
8126
|
+
return this.ruleRegistry.getSchemaRule(url, context, (url) => this.parseSchemaUrl(url));
|
|
8127
|
+
}
|
|
8128
|
+
getToolbarActions(context) {
|
|
8129
|
+
return this.toolbarController.getToolbarActions(context);
|
|
8130
|
+
}
|
|
8131
|
+
parseSchemaUrl(url) {
|
|
8132
|
+
try {
|
|
8133
|
+
const parsedUrl = new URL(url);
|
|
8134
|
+
return {
|
|
8135
|
+
hash: parsedUrl.hash,
|
|
8136
|
+
host: parsedUrl.host,
|
|
8137
|
+
params: Object.fromEntries(parsedUrl.searchParams.entries()),
|
|
8138
|
+
pathname: parsedUrl.pathname,
|
|
8139
|
+
protocol: parsedUrl.protocol,
|
|
8140
|
+
raw: url,
|
|
8141
|
+
search: parsedUrl.search
|
|
8142
|
+
};
|
|
8143
|
+
} catch {
|
|
8144
|
+
return null;
|
|
8145
|
+
}
|
|
8146
|
+
}
|
|
8147
|
+
updateConfig(config) {
|
|
8148
|
+
this.setAllowedProtocols([
|
|
8149
|
+
"http:",
|
|
8150
|
+
"https:",
|
|
8151
|
+
"mailto:",
|
|
8152
|
+
"sms:",
|
|
8153
|
+
"tel:",
|
|
8154
|
+
...config?.schemaLinkRenderers?.map(({ protocol }) => protocol) || [],
|
|
8155
|
+
...config?.allowedProtocols || []
|
|
8156
|
+
]);
|
|
8157
|
+
this.setEmbedRules(config?.linkEmbedRules);
|
|
8158
|
+
this.setLabels(config?.labels);
|
|
8159
|
+
this.setSchemaLinkRenderers(config?.schemaLinkRenderers);
|
|
8160
|
+
this.setSchemaRules([...config?.schemaRules || [], ...createLegacySchemaRules(config?.schemaLinkRenderers)]);
|
|
8161
|
+
this.setToolbarActions(config?.toolbarActions);
|
|
8162
|
+
}
|
|
8163
|
+
setAllowedProtocols(protocols) {
|
|
8164
|
+
this.protocolPolicy.setAllowedProtocols(protocols);
|
|
8165
|
+
}
|
|
8166
|
+
setEmbedRules(rules = []) {
|
|
8167
|
+
this.ruleRegistry.setEmbedRules(rules);
|
|
8168
|
+
}
|
|
8169
|
+
setLabels(labels) {
|
|
8170
|
+
this.toolbarController.setLabels(labels);
|
|
8171
|
+
}
|
|
8172
|
+
setSchemaLinkRenderers(renderers = []) {
|
|
8173
|
+
this.legacySchemaLinkRegistry.setSchemaLinkRenderers(renderers);
|
|
8174
|
+
}
|
|
8175
|
+
setSchemaRules(rules = []) {
|
|
8176
|
+
this.ruleRegistry.setSchemaRules(rules);
|
|
8177
|
+
}
|
|
8178
|
+
setToolbarActions(actions = []) {
|
|
8179
|
+
this.toolbarController.setToolbarActions(actions);
|
|
8180
|
+
this.emit("change");
|
|
8181
|
+
}
|
|
8182
|
+
getToolbarItems(context) {
|
|
8183
|
+
return Array.from(this.toolbarItems.values()).filter((item) => item.when ? item.when(context) : true).sort((a, b) => (a.order || 0) - (b.order || 0));
|
|
8184
|
+
}
|
|
8185
|
+
registerToolbarItem(item) {
|
|
8186
|
+
this.toolbarItems.set(item.key, item);
|
|
8187
|
+
this.emit("change");
|
|
8188
|
+
return () => {
|
|
8189
|
+
this.toolbarItems.delete(item.key);
|
|
8190
|
+
this.emit("change");
|
|
8191
|
+
};
|
|
8192
|
+
}
|
|
8193
|
+
setLinkToolbar(enable) {
|
|
8194
|
+
if (this.toolbarController.setEnabled(enable)) this.emit("linkToolbarChange", enable);
|
|
8195
|
+
}
|
|
8196
|
+
suppressLinkToolbar(reason = "unknown") {
|
|
8197
|
+
const { changed, token } = this.toolbarController.suppress(reason);
|
|
8198
|
+
if (changed) this.emit("linkToolbarChange", this.toolbarController.enabled);
|
|
8199
|
+
return token;
|
|
8200
|
+
}
|
|
8201
|
+
restoreLinkToolbar(token) {
|
|
8202
|
+
if (this.toolbarController.restore(token)) this.emit("linkToolbarChange", this.toolbarController.enabled);
|
|
8203
|
+
}
|
|
8204
|
+
subscribe(listener) {
|
|
8205
|
+
this.on("change", listener);
|
|
8206
|
+
return () => {
|
|
8207
|
+
this.off("change", listener);
|
|
8208
|
+
};
|
|
8209
|
+
}
|
|
8210
|
+
subscribeCardMetadata(listener) {
|
|
8211
|
+
this.on("cardMetadataChange", listener);
|
|
8212
|
+
return () => {
|
|
8213
|
+
this.off("cardMetadataChange", listener);
|
|
8214
|
+
};
|
|
8215
|
+
}
|
|
8216
|
+
};
|
|
8217
|
+
function normalizeProtocol(protocol) {
|
|
8218
|
+
return `${protocol.split(":")[0]}:`;
|
|
8219
|
+
}
|
|
8220
|
+
function createLegacySchemaRules(renderers = []) {
|
|
8221
|
+
return renderers.map(({ protocol }) => {
|
|
8222
|
+
const normalizedProtocol = normalizeProtocol(protocol);
|
|
8223
|
+
return {
|
|
8224
|
+
id: normalizedProtocol.replace(":", ""),
|
|
8225
|
+
match: (url) => {
|
|
8226
|
+
try {
|
|
8227
|
+
return new URL(url).protocol === normalizedProtocol;
|
|
8228
|
+
} catch {
|
|
8229
|
+
return false;
|
|
8230
|
+
}
|
|
8231
|
+
}
|
|
8232
|
+
};
|
|
8233
|
+
});
|
|
8234
|
+
}
|
|
8235
|
+
//#endregion
|
|
6765
8236
|
//#region src/plugins/link/node/LinkNode.ts
|
|
6766
8237
|
const logger = createDebugLogger("plugin", "link");
|
|
6767
8238
|
const SUPPORTED_URL_PROTOCOLS$1 = /* @__PURE__ */ new Set([
|
|
@@ -6803,7 +8274,7 @@ var LinkNode = class LinkNode extends ElementNode {
|
|
|
6803
8274
|
event.target.classList.add("hover");
|
|
6804
8275
|
editor.dispatchCommand(HOVER_LINK_COMMAND, {
|
|
6805
8276
|
event,
|
|
6806
|
-
|
|
8277
|
+
node: this
|
|
6807
8278
|
});
|
|
6808
8279
|
}
|
|
6809
8280
|
});
|
|
@@ -6817,7 +8288,8 @@ var LinkNode = class LinkNode extends ElementNode {
|
|
|
6817
8288
|
}
|
|
6818
8289
|
updateLinkDOM(prevNode, anchor, _config) {
|
|
6819
8290
|
if (isHTMLAnchorElement(anchor)) {
|
|
6820
|
-
|
|
8291
|
+
const linkService = getLinkServiceFromConfig(_config);
|
|
8292
|
+
if (!prevNode || prevNode.__url !== this.__url) anchor.href = this.sanitizeUrl(this.__url, linkService?.getAllowedProtocols());
|
|
6821
8293
|
for (const attr of [
|
|
6822
8294
|
"target",
|
|
6823
8295
|
"rel",
|
|
@@ -6848,11 +8320,11 @@ var LinkNode = class LinkNode extends ElementNode {
|
|
|
6848
8320
|
updateFromJSON(serializedNode) {
|
|
6849
8321
|
return super.updateFromJSON(serializedNode).setURL(serializedNode.url).setRel(serializedNode.rel || null).setTarget(serializedNode.target || null).setTitle(serializedNode.title || null);
|
|
6850
8322
|
}
|
|
6851
|
-
sanitizeUrl(url) {
|
|
8323
|
+
sanitizeUrl(url, allowedProtocols = SUPPORTED_URL_PROTOCOLS$1) {
|
|
6852
8324
|
url = formatUrl(url);
|
|
6853
8325
|
try {
|
|
6854
8326
|
const parsedUrl = new URL(formatUrl(url));
|
|
6855
|
-
if (!
|
|
8327
|
+
if (!allowedProtocols.has(parsedUrl.protocol)) return "about:blank";
|
|
6856
8328
|
} catch {
|
|
6857
8329
|
return url;
|
|
6858
8330
|
}
|
|
@@ -6932,7 +8404,13 @@ var LinkNode = class LinkNode extends ElementNode {
|
|
|
6932
8404
|
isWebSiteURI() {
|
|
6933
8405
|
return this.__url.startsWith("https://") || this.__url.startsWith("http://");
|
|
6934
8406
|
}
|
|
8407
|
+
getDOMSlot(element) {
|
|
8408
|
+
return super.getDOMSlot(element);
|
|
8409
|
+
}
|
|
6935
8410
|
};
|
|
8411
|
+
function getLinkServiceFromConfig(config) {
|
|
8412
|
+
return getKernelFromEditorConfig(config)?.requireService(ILinkService) || null;
|
|
8413
|
+
}
|
|
6936
8414
|
function $convertAnchorElement(domNode) {
|
|
6937
8415
|
let node = null;
|
|
6938
8416
|
if (isHTMLAnchorElement(domNode)) {
|
|
@@ -6960,7 +8438,7 @@ function $createLinkNode(url = "", attributes) {
|
|
|
6960
8438
|
* @returns true if node is a LinkNode, false otherwise.
|
|
6961
8439
|
*/
|
|
6962
8440
|
function $isLinkNode(node) {
|
|
6963
|
-
return node instanceof LinkNode;
|
|
8441
|
+
return node instanceof LinkNode || node?.getType?.() === "link" || node?.getType?.() === "autolink";
|
|
6964
8442
|
}
|
|
6965
8443
|
var AutoLinkNode = class AutoLinkNode extends LinkNode {
|
|
6966
8444
|
constructor(url = "", attributes = {}, key) {
|
|
@@ -7208,6 +8686,7 @@ function formatUrl(url) {
|
|
|
7208
8686
|
//#region src/plugins/link/command/index.ts
|
|
7209
8687
|
const INSERT_LINK_COMMAND = createCommand("INSERT_LINK_COMMAND");
|
|
7210
8688
|
const UPDATE_LINK_TEXT_COMMAND = createCommand("UPDATE_LINK_TEXT_COMMAND");
|
|
8689
|
+
const UNLINK_LINK_COMMAND = createCommand("UNLINK_LINK_COMMAND");
|
|
7211
8690
|
function registerLinkCommand(editor) {
|
|
7212
8691
|
return mergeRegister(editor.registerCommand(INSERT_LINK_COMMAND, (payload) => {
|
|
7213
8692
|
const { url, title = url } = payload;
|
|
@@ -7217,38 +8696,564 @@ function registerLinkCommand(editor) {
|
|
|
7217
8696
|
linkNode.append(textNode);
|
|
7218
8697
|
$insertNodes([linkNode]);
|
|
7219
8698
|
});
|
|
7220
|
-
return false;
|
|
7221
|
-
}, COMMAND_PRIORITY_EDITOR), editor.registerCommand(UPDATE_LINK_TEXT_COMMAND, (payload) => {
|
|
7222
|
-
const { key, text } = payload;
|
|
7223
|
-
editor.update(() => {
|
|
7224
|
-
const linkNode = $getNodeByKey(key);
|
|
7225
|
-
if (linkNode) {
|
|
7226
|
-
const newLinkNode = $createLinkNode(linkNode.getURL(), { title: text });
|
|
7227
|
-
const textNode = $createTextNode(text);
|
|
7228
|
-
newLinkNode.append(textNode);
|
|
7229
|
-
linkNode?.replace(newLinkNode);
|
|
7230
|
-
newLinkNode.select(1);
|
|
8699
|
+
return false;
|
|
8700
|
+
}, COMMAND_PRIORITY_EDITOR), editor.registerCommand(UPDATE_LINK_TEXT_COMMAND, (payload) => {
|
|
8701
|
+
const { key, text } = payload;
|
|
8702
|
+
editor.update(() => {
|
|
8703
|
+
const linkNode = $getNodeByKey(key);
|
|
8704
|
+
if (linkNode) {
|
|
8705
|
+
const newLinkNode = $createLinkNode(linkNode.getURL(), { title: text });
|
|
8706
|
+
const textNode = $createTextNode(text);
|
|
8707
|
+
newLinkNode.append(textNode);
|
|
8708
|
+
linkNode?.replace(newLinkNode);
|
|
8709
|
+
newLinkNode.select(1);
|
|
8710
|
+
}
|
|
8711
|
+
});
|
|
8712
|
+
return false;
|
|
8713
|
+
}, COMMAND_PRIORITY_EDITOR), editor.registerCommand(UNLINK_LINK_COMMAND, ({ key }) => {
|
|
8714
|
+
editor.update(() => {
|
|
8715
|
+
const node = $getNodeByKey(key);
|
|
8716
|
+
if (!$isLinkNode(node)) return;
|
|
8717
|
+
let selection = $getSelection();
|
|
8718
|
+
if (!selection || !$isRangeSelection(selection)) {
|
|
8719
|
+
$setSelection($createRangeSelection());
|
|
8720
|
+
selection = $getSelection();
|
|
8721
|
+
}
|
|
8722
|
+
const first = node.getFirstDescendant();
|
|
8723
|
+
const last = node.getLastDescendant();
|
|
8724
|
+
if (selection && $isRangeSelection(selection) && $isTextNode(first) && $isTextNode(last)) {
|
|
8725
|
+
selection.anchor.set(first.getKey(), 0, "text");
|
|
8726
|
+
selection.focus.set(last.getKey(), last.getTextContentSize(), "text");
|
|
8727
|
+
editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
|
|
8728
|
+
return;
|
|
8729
|
+
}
|
|
8730
|
+
const children = node.getChildren();
|
|
8731
|
+
for (const child of children) node.insertBefore(child);
|
|
8732
|
+
node.remove();
|
|
8733
|
+
});
|
|
8734
|
+
return true;
|
|
8735
|
+
}, COMMAND_PRIORITY_EDITOR));
|
|
8736
|
+
}
|
|
8737
|
+
createCommand();
|
|
8738
|
+
var LinkCardNode = class LinkCardNode extends DecoratorNode {
|
|
8739
|
+
static getType() {
|
|
8740
|
+
return "link-card";
|
|
8741
|
+
}
|
|
8742
|
+
static clone(node) {
|
|
8743
|
+
return new LinkCardNode(node.__url, node.__title, node.__icon, node.__description, node.__openTarget, node.__key);
|
|
8744
|
+
}
|
|
8745
|
+
static importJSON(serializedNode) {
|
|
8746
|
+
return $createLinkCardNode({
|
|
8747
|
+
description: serializedNode.description,
|
|
8748
|
+
icon: serializedNode.icon,
|
|
8749
|
+
openTarget: serializedNode.openTarget,
|
|
8750
|
+
title: serializedNode.title,
|
|
8751
|
+
url: serializedNode.url
|
|
8752
|
+
}).updateFromJSON(serializedNode);
|
|
8753
|
+
}
|
|
8754
|
+
static importDOM() {
|
|
8755
|
+
return { a: (node) => {
|
|
8756
|
+
if (node instanceof HTMLAnchorElement && node.dataset.linkCard === "true" && node.dataset.linkCardLayout !== "block") return {
|
|
8757
|
+
conversion: $convertLinkCardElement,
|
|
8758
|
+
priority: 2
|
|
8759
|
+
};
|
|
8760
|
+
return null;
|
|
8761
|
+
} };
|
|
8762
|
+
}
|
|
8763
|
+
constructor(url, title, icon, description, openTarget, key) {
|
|
8764
|
+
super(key);
|
|
8765
|
+
this.__url = url;
|
|
8766
|
+
this.__title = title || url;
|
|
8767
|
+
this.__icon = icon || "";
|
|
8768
|
+
this.__description = description || "";
|
|
8769
|
+
this.__openTarget = openTarget ?? "_blank";
|
|
8770
|
+
}
|
|
8771
|
+
createDOM(config, editor) {
|
|
8772
|
+
const element = document.createElement("span");
|
|
8773
|
+
element.dataset.linkCard = "true";
|
|
8774
|
+
addClassNamesToElement(element, config.theme.linkCard);
|
|
8775
|
+
element.addEventListener("mouseenter", (event) => {
|
|
8776
|
+
if (event.target instanceof HTMLElement) {
|
|
8777
|
+
event.target.classList.add("hover");
|
|
8778
|
+
editor.dispatchCommand(HOVER_LINK_COMMAND, {
|
|
8779
|
+
event,
|
|
8780
|
+
node: this
|
|
8781
|
+
});
|
|
8782
|
+
}
|
|
8783
|
+
});
|
|
8784
|
+
element.addEventListener("mouseleave", (event) => {
|
|
8785
|
+
if (event.target instanceof HTMLElement) {
|
|
8786
|
+
event.target.classList.remove("hover");
|
|
8787
|
+
editor.dispatchCommand(HOVER_OUT_LINK_COMMAND, { event });
|
|
8788
|
+
}
|
|
8789
|
+
});
|
|
8790
|
+
return element;
|
|
8791
|
+
}
|
|
8792
|
+
updateDOM() {
|
|
8793
|
+
return false;
|
|
8794
|
+
}
|
|
8795
|
+
isInline() {
|
|
8796
|
+
return true;
|
|
8797
|
+
}
|
|
8798
|
+
isKeyboardSelectable() {
|
|
8799
|
+
return true;
|
|
8800
|
+
}
|
|
8801
|
+
exportDOM() {
|
|
8802
|
+
const element = document.createElement("a");
|
|
8803
|
+
element.dataset.linkCard = "true";
|
|
8804
|
+
element.dataset.icon = this.getIcon();
|
|
8805
|
+
element.dataset.description = this.getDescription();
|
|
8806
|
+
if (this.getOpenTarget()) element.target = this.getOpenTarget();
|
|
8807
|
+
element.href = this.getURL();
|
|
8808
|
+
element.textContent = this.getTitle();
|
|
8809
|
+
return { element };
|
|
8810
|
+
}
|
|
8811
|
+
exportJSON() {
|
|
8812
|
+
return {
|
|
8813
|
+
...super.exportJSON(),
|
|
8814
|
+
description: this.getDescription(),
|
|
8815
|
+
icon: this.getIcon(),
|
|
8816
|
+
openTarget: this.getOpenTarget(),
|
|
8817
|
+
title: this.getTitle(),
|
|
8818
|
+
url: this.getURL()
|
|
8819
|
+
};
|
|
8820
|
+
}
|
|
8821
|
+
updateFromJSON(serializedNode) {
|
|
8822
|
+
return super.updateFromJSON(serializedNode).setURL(serializedNode.url).setTitle(serializedNode.title).setIcon(serializedNode.icon).setDescription(serializedNode.description).setOpenTarget(serializedNode.openTarget ?? "_blank");
|
|
8823
|
+
}
|
|
8824
|
+
getTextContent() {
|
|
8825
|
+
return this.getTitle();
|
|
8826
|
+
}
|
|
8827
|
+
getURL() {
|
|
8828
|
+
return this.getLatest().__url;
|
|
8829
|
+
}
|
|
8830
|
+
setURL(url) {
|
|
8831
|
+
const writable = this.getWritable();
|
|
8832
|
+
writable.__url = url;
|
|
8833
|
+
return writable;
|
|
8834
|
+
}
|
|
8835
|
+
getTitle() {
|
|
8836
|
+
return this.getLatest().__title || this.getURL();
|
|
8837
|
+
}
|
|
8838
|
+
setTitle(title) {
|
|
8839
|
+
const writable = this.getWritable();
|
|
8840
|
+
writable.__title = title || this.getURL();
|
|
8841
|
+
return writable;
|
|
8842
|
+
}
|
|
8843
|
+
getIcon() {
|
|
8844
|
+
return this.getLatest().__icon;
|
|
8845
|
+
}
|
|
8846
|
+
setIcon(icon) {
|
|
8847
|
+
const writable = this.getWritable();
|
|
8848
|
+
writable.__icon = icon || "";
|
|
8849
|
+
return writable;
|
|
8850
|
+
}
|
|
8851
|
+
getDescription() {
|
|
8852
|
+
return this.getLatest().__description;
|
|
8853
|
+
}
|
|
8854
|
+
setDescription(description) {
|
|
8855
|
+
const writable = this.getWritable();
|
|
8856
|
+
writable.__description = description || "";
|
|
8857
|
+
return writable;
|
|
8858
|
+
}
|
|
8859
|
+
getOpenTarget() {
|
|
8860
|
+
return this.getLatest().__openTarget;
|
|
8861
|
+
}
|
|
8862
|
+
setOpenTarget(openTarget) {
|
|
8863
|
+
const writable = this.getWritable();
|
|
8864
|
+
writable.__openTarget = openTarget ?? "_blank";
|
|
8865
|
+
return writable;
|
|
8866
|
+
}
|
|
8867
|
+
decorate(editor) {
|
|
8868
|
+
const decorator = getKernelFromEditor(editor)?.getDecorator(LinkCardNode.getType());
|
|
8869
|
+
if (!decorator) return null;
|
|
8870
|
+
if (typeof decorator === "function") return decorator(this, editor);
|
|
8871
|
+
return decorator.render(this, editor);
|
|
8872
|
+
}
|
|
8873
|
+
};
|
|
8874
|
+
function $createLinkCardNode(payload) {
|
|
8875
|
+
return $applyNodeReplacement(new LinkCardNode(payload.url, payload.title, payload.icon, payload.description, payload.openTarget));
|
|
8876
|
+
}
|
|
8877
|
+
function $convertLinkCardElement(domNode) {
|
|
8878
|
+
const element = domNode;
|
|
8879
|
+
return { node: $createLinkCardNode({
|
|
8880
|
+
description: element.dataset.description,
|
|
8881
|
+
icon: element.dataset.icon,
|
|
8882
|
+
openTarget: element.getAttribute("target"),
|
|
8883
|
+
title: element.textContent || element.href,
|
|
8884
|
+
url: element.getAttribute("href") || ""
|
|
8885
|
+
}) };
|
|
8886
|
+
}
|
|
8887
|
+
function $isLinkCardNode(node) {
|
|
8888
|
+
return node instanceof LinkCardNode || node?.getType?.() === LinkCardNode.getType();
|
|
8889
|
+
}
|
|
8890
|
+
//#endregion
|
|
8891
|
+
//#region src/plugins/link/node/LinkBlockCardNode.ts
|
|
8892
|
+
var LinkBlockCardNode = class LinkBlockCardNode extends LinkCardNode {
|
|
8893
|
+
static getType() {
|
|
8894
|
+
return "link-block-card";
|
|
8895
|
+
}
|
|
8896
|
+
static clone(node) {
|
|
8897
|
+
return new LinkBlockCardNode(node.__url, node.__title, node.__icon, node.__description, node.__openTarget, node.__key);
|
|
8898
|
+
}
|
|
8899
|
+
static importJSON(serializedNode) {
|
|
8900
|
+
return $createLinkBlockCardNode({
|
|
8901
|
+
description: serializedNode.description,
|
|
8902
|
+
icon: serializedNode.icon,
|
|
8903
|
+
openTarget: serializedNode.openTarget,
|
|
8904
|
+
title: serializedNode.title,
|
|
8905
|
+
url: serializedNode.url
|
|
8906
|
+
}).updateFromJSON(serializedNode);
|
|
8907
|
+
}
|
|
8908
|
+
static importDOM() {
|
|
8909
|
+
return { a: (node) => {
|
|
8910
|
+
if (node instanceof HTMLAnchorElement && node.dataset.linkCard === "true" && node.dataset.linkCardLayout === "block") return {
|
|
8911
|
+
conversion: $convertLinkBlockCardElement,
|
|
8912
|
+
priority: 3
|
|
8913
|
+
};
|
|
8914
|
+
return null;
|
|
8915
|
+
} };
|
|
8916
|
+
}
|
|
8917
|
+
constructor(url, title, icon, description, openTarget, key) {
|
|
8918
|
+
super(url, title, icon, description, openTarget, key);
|
|
8919
|
+
}
|
|
8920
|
+
createDOM(config, editor) {
|
|
8921
|
+
const element = super.createDOM(config, editor);
|
|
8922
|
+
element.dataset.linkCardLayout = "block";
|
|
8923
|
+
return element;
|
|
8924
|
+
}
|
|
8925
|
+
exportDOM() {
|
|
8926
|
+
const output = super.exportDOM();
|
|
8927
|
+
if (output.element instanceof HTMLElement) output.element.dataset.linkCardLayout = "block";
|
|
8928
|
+
return output;
|
|
8929
|
+
}
|
|
8930
|
+
updateFromJSON(serializedNode) {
|
|
8931
|
+
return super.updateFromJSON(serializedNode);
|
|
8932
|
+
}
|
|
8933
|
+
isInline() {
|
|
8934
|
+
return false;
|
|
8935
|
+
}
|
|
8936
|
+
};
|
|
8937
|
+
function $createLinkBlockCardNode(payload) {
|
|
8938
|
+
return $applyNodeReplacement(new LinkBlockCardNode(payload.url, payload.title, payload.icon, payload.description, payload.openTarget));
|
|
8939
|
+
}
|
|
8940
|
+
function $convertLinkBlockCardElement(domNode) {
|
|
8941
|
+
const element = domNode;
|
|
8942
|
+
return { node: $createLinkBlockCardNode({
|
|
8943
|
+
description: element.dataset.description,
|
|
8944
|
+
icon: element.dataset.icon,
|
|
8945
|
+
openTarget: element.getAttribute("target"),
|
|
8946
|
+
title: element.textContent || element.href,
|
|
8947
|
+
url: element.getAttribute("href") || ""
|
|
8948
|
+
}) };
|
|
8949
|
+
}
|
|
8950
|
+
function $isLinkBlockCardNode(node) {
|
|
8951
|
+
return node instanceof LinkBlockCardNode || node?.getType?.() === LinkBlockCardNode.getType();
|
|
8952
|
+
}
|
|
8953
|
+
//#endregion
|
|
8954
|
+
//#region src/plugins/link/node/LinkIframeNode.ts
|
|
8955
|
+
var LinkIframeNode = class LinkIframeNode extends DecoratorNode {
|
|
8956
|
+
static getType() {
|
|
8957
|
+
return "link-iframe";
|
|
8958
|
+
}
|
|
8959
|
+
static clone(node) {
|
|
8960
|
+
return new LinkIframeNode(node.__url, node.__src, node.__title, node.__key);
|
|
8961
|
+
}
|
|
8962
|
+
static importJSON(serializedNode) {
|
|
8963
|
+
return $createLinkIframeNode({
|
|
8964
|
+
src: serializedNode.src,
|
|
8965
|
+
title: serializedNode.title,
|
|
8966
|
+
url: serializedNode.url
|
|
8967
|
+
}).updateFromJSON(serializedNode);
|
|
8968
|
+
}
|
|
8969
|
+
static importDOM() {
|
|
8970
|
+
return { a: (node) => {
|
|
8971
|
+
if (node instanceof HTMLAnchorElement && node.dataset.linkIframe === "true") return {
|
|
8972
|
+
conversion: $convertLinkIframeElement,
|
|
8973
|
+
priority: 2
|
|
8974
|
+
};
|
|
8975
|
+
return null;
|
|
8976
|
+
} };
|
|
8977
|
+
}
|
|
8978
|
+
constructor(url, src, title, key) {
|
|
8979
|
+
super(key);
|
|
8980
|
+
this.__url = url;
|
|
8981
|
+
this.__src = src || url;
|
|
8982
|
+
this.__title = title || url;
|
|
8983
|
+
}
|
|
8984
|
+
createDOM(config, editor) {
|
|
8985
|
+
const element = document.createElement("div");
|
|
8986
|
+
element.dataset.linkIframe = "true";
|
|
8987
|
+
addClassNamesToElement(element, config.theme.linkIframe);
|
|
8988
|
+
element.addEventListener("mouseenter", (event) => {
|
|
8989
|
+
if (event.target instanceof HTMLElement) {
|
|
8990
|
+
event.target.classList.add("hover");
|
|
8991
|
+
editor.dispatchCommand(HOVER_LINK_COMMAND, {
|
|
8992
|
+
event,
|
|
8993
|
+
node: this
|
|
8994
|
+
});
|
|
8995
|
+
}
|
|
8996
|
+
});
|
|
8997
|
+
element.addEventListener("mouseleave", (event) => {
|
|
8998
|
+
if (event.target instanceof HTMLElement) {
|
|
8999
|
+
event.target.classList.remove("hover");
|
|
9000
|
+
editor.dispatchCommand(HOVER_OUT_LINK_COMMAND, { event });
|
|
9001
|
+
}
|
|
9002
|
+
});
|
|
9003
|
+
return element;
|
|
9004
|
+
}
|
|
9005
|
+
updateDOM() {
|
|
9006
|
+
return false;
|
|
9007
|
+
}
|
|
9008
|
+
isInline() {
|
|
9009
|
+
return false;
|
|
9010
|
+
}
|
|
9011
|
+
exportDOM() {
|
|
9012
|
+
const element = document.createElement("a");
|
|
9013
|
+
element.dataset.linkIframe = "true";
|
|
9014
|
+
element.dataset.src = this.getSrc();
|
|
9015
|
+
element.href = this.getURL();
|
|
9016
|
+
element.textContent = this.getTitle();
|
|
9017
|
+
return { element };
|
|
9018
|
+
}
|
|
9019
|
+
exportJSON() {
|
|
9020
|
+
return {
|
|
9021
|
+
...super.exportJSON(),
|
|
9022
|
+
src: this.getSrc(),
|
|
9023
|
+
title: this.getTitle(),
|
|
9024
|
+
url: this.getURL()
|
|
9025
|
+
};
|
|
9026
|
+
}
|
|
9027
|
+
updateFromJSON(serializedNode) {
|
|
9028
|
+
return super.updateFromJSON(serializedNode).setURL(serializedNode.url).setSrc(serializedNode.src).setTitle(serializedNode.title);
|
|
9029
|
+
}
|
|
9030
|
+
getTextContent() {
|
|
9031
|
+
return this.getTitle();
|
|
9032
|
+
}
|
|
9033
|
+
getURL() {
|
|
9034
|
+
return this.getLatest().__url;
|
|
9035
|
+
}
|
|
9036
|
+
setURL(url) {
|
|
9037
|
+
const writable = this.getWritable();
|
|
9038
|
+
writable.__url = url;
|
|
9039
|
+
return writable;
|
|
9040
|
+
}
|
|
9041
|
+
getSrc() {
|
|
9042
|
+
return this.getLatest().__src || this.getURL();
|
|
9043
|
+
}
|
|
9044
|
+
setSrc(src) {
|
|
9045
|
+
const writable = this.getWritable();
|
|
9046
|
+
writable.__src = src || this.getURL();
|
|
9047
|
+
return writable;
|
|
9048
|
+
}
|
|
9049
|
+
getTitle() {
|
|
9050
|
+
return this.getLatest().__title || this.getURL();
|
|
9051
|
+
}
|
|
9052
|
+
setTitle(title) {
|
|
9053
|
+
const writable = this.getWritable();
|
|
9054
|
+
writable.__title = title || this.getURL();
|
|
9055
|
+
return writable;
|
|
9056
|
+
}
|
|
9057
|
+
decorate(editor) {
|
|
9058
|
+
const decorator = getKernelFromEditor(editor)?.getDecorator(LinkIframeNode.getType());
|
|
9059
|
+
if (!decorator) return null;
|
|
9060
|
+
if (typeof decorator === "function") return decorator(this, editor);
|
|
9061
|
+
return decorator.render(this, editor);
|
|
9062
|
+
}
|
|
9063
|
+
};
|
|
9064
|
+
function $createLinkIframeNode(payload) {
|
|
9065
|
+
return $applyNodeReplacement(new LinkIframeNode(payload.url, payload.src, payload.title));
|
|
9066
|
+
}
|
|
9067
|
+
function $convertLinkIframeElement(domNode) {
|
|
9068
|
+
const element = domNode;
|
|
9069
|
+
return { node: $createLinkIframeNode({
|
|
9070
|
+
src: element.dataset.src,
|
|
9071
|
+
title: element.textContent || element.href,
|
|
9072
|
+
url: element.getAttribute("href") || ""
|
|
9073
|
+
}) };
|
|
9074
|
+
}
|
|
9075
|
+
function $isLinkIframeNode(node) {
|
|
9076
|
+
return node instanceof LinkIframeNode || node?.getType?.() === LinkIframeNode.getType();
|
|
9077
|
+
}
|
|
9078
|
+
//#endregion
|
|
9079
|
+
//#region src/plugins/link/node/SchemaNode.ts
|
|
9080
|
+
var SchemaNode = class SchemaNode extends DecoratorNode {
|
|
9081
|
+
static getType() {
|
|
9082
|
+
return "schema-link";
|
|
9083
|
+
}
|
|
9084
|
+
static clone(node) {
|
|
9085
|
+
return new SchemaNode(node.__url, node.__schemaType, node.__payload, node.__title, node.__key);
|
|
9086
|
+
}
|
|
9087
|
+
static importJSON(serializedNode) {
|
|
9088
|
+
return $createSchemaNode({
|
|
9089
|
+
payload: serializedNode.payload,
|
|
9090
|
+
schemaType: serializedNode.schemaType,
|
|
9091
|
+
title: serializedNode.title,
|
|
9092
|
+
url: serializedNode.url
|
|
9093
|
+
}).updateFromJSON(serializedNode);
|
|
9094
|
+
}
|
|
9095
|
+
static importDOM() {
|
|
9096
|
+
return { a: (node) => {
|
|
9097
|
+
if (node instanceof HTMLAnchorElement && node.dataset.schemaLink === "true") return {
|
|
9098
|
+
conversion: $convertSchemaElement,
|
|
9099
|
+
priority: 2
|
|
9100
|
+
};
|
|
9101
|
+
return null;
|
|
9102
|
+
} };
|
|
9103
|
+
}
|
|
9104
|
+
constructor(url, schemaType, payload, title, key) {
|
|
9105
|
+
super(key);
|
|
9106
|
+
this.__url = url;
|
|
9107
|
+
this.__schemaType = schemaType || "";
|
|
9108
|
+
this.__payload = payload;
|
|
9109
|
+
this.__title = title || url;
|
|
9110
|
+
}
|
|
9111
|
+
createDOM(config, editor) {
|
|
9112
|
+
const element = document.createElement("div");
|
|
9113
|
+
element.dataset.schemaLink = "true";
|
|
9114
|
+
addClassNamesToElement(element, config.theme.schemaLink);
|
|
9115
|
+
element.addEventListener("mouseenter", (event) => {
|
|
9116
|
+
if (event.target instanceof HTMLElement) {
|
|
9117
|
+
event.target.classList.add("hover");
|
|
9118
|
+
editor.dispatchCommand(HOVER_LINK_COMMAND, {
|
|
9119
|
+
event,
|
|
9120
|
+
node: this
|
|
9121
|
+
});
|
|
9122
|
+
}
|
|
9123
|
+
});
|
|
9124
|
+
element.addEventListener("mouseleave", (event) => {
|
|
9125
|
+
if (event.target instanceof HTMLElement) {
|
|
9126
|
+
event.target.classList.remove("hover");
|
|
9127
|
+
editor.dispatchCommand(HOVER_OUT_LINK_COMMAND, { event });
|
|
7231
9128
|
}
|
|
7232
9129
|
});
|
|
9130
|
+
return element;
|
|
9131
|
+
}
|
|
9132
|
+
updateDOM() {
|
|
7233
9133
|
return false;
|
|
7234
|
-
}, COMMAND_PRIORITY_EDITOR));
|
|
7235
|
-
}
|
|
7236
|
-
//#endregion
|
|
7237
|
-
//#region src/plugins/link/service/i-link-service.ts
|
|
7238
|
-
const ILinkService = genServiceId("LinkService");
|
|
7239
|
-
var LinkService = class extends EventEmitter {
|
|
7240
|
-
constructor(..._args) {
|
|
7241
|
-
super(..._args);
|
|
7242
|
-
this._enableLinkToolbar = true;
|
|
7243
9134
|
}
|
|
7244
|
-
|
|
7245
|
-
|
|
9135
|
+
exportDOM() {
|
|
9136
|
+
const element = document.createElement("a");
|
|
9137
|
+
element.dataset.schemaLink = "true";
|
|
9138
|
+
element.dataset.schemaType = this.getSchemaType();
|
|
9139
|
+
element.dataset.payload = JSON.stringify(this.getPayload() ?? null);
|
|
9140
|
+
element.href = this.getURL();
|
|
9141
|
+
element.textContent = this.getTitle();
|
|
9142
|
+
return { element };
|
|
7246
9143
|
}
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
|
|
9144
|
+
exportJSON() {
|
|
9145
|
+
return {
|
|
9146
|
+
...super.exportJSON(),
|
|
9147
|
+
payload: this.getPayload(),
|
|
9148
|
+
schemaType: this.getSchemaType(),
|
|
9149
|
+
title: this.getTitle(),
|
|
9150
|
+
url: this.getURL()
|
|
9151
|
+
};
|
|
9152
|
+
}
|
|
9153
|
+
updateFromJSON(serializedNode) {
|
|
9154
|
+
return super.updateFromJSON(serializedNode).setURL(serializedNode.url).setSchemaType(serializedNode.schemaType).setPayload(serializedNode.payload).setTitle(serializedNode.title);
|
|
9155
|
+
}
|
|
9156
|
+
getTextContent() {
|
|
9157
|
+
return this.getTitle();
|
|
9158
|
+
}
|
|
9159
|
+
getURL() {
|
|
9160
|
+
return this.getLatest().__url;
|
|
9161
|
+
}
|
|
9162
|
+
setURL(url) {
|
|
9163
|
+
const writable = this.getWritable();
|
|
9164
|
+
writable.__url = url;
|
|
9165
|
+
return writable;
|
|
9166
|
+
}
|
|
9167
|
+
getSchemaType() {
|
|
9168
|
+
return this.getLatest().__schemaType;
|
|
9169
|
+
}
|
|
9170
|
+
setSchemaType(schemaType) {
|
|
9171
|
+
const writable = this.getWritable();
|
|
9172
|
+
writable.__schemaType = schemaType || "";
|
|
9173
|
+
return writable;
|
|
9174
|
+
}
|
|
9175
|
+
getPayload() {
|
|
9176
|
+
return this.getLatest().__payload;
|
|
9177
|
+
}
|
|
9178
|
+
setPayload(payload) {
|
|
9179
|
+
const writable = this.getWritable();
|
|
9180
|
+
writable.__payload = payload;
|
|
9181
|
+
return writable;
|
|
9182
|
+
}
|
|
9183
|
+
getTitle() {
|
|
9184
|
+
return this.getLatest().__title || this.getURL();
|
|
9185
|
+
}
|
|
9186
|
+
setTitle(title) {
|
|
9187
|
+
const writable = this.getWritable();
|
|
9188
|
+
writable.__title = title || this.getURL();
|
|
9189
|
+
return writable;
|
|
9190
|
+
}
|
|
9191
|
+
decorate(editor) {
|
|
9192
|
+
const decorator = getKernelFromEditor(editor)?.getDecorator(SchemaNode.getType());
|
|
9193
|
+
if (!decorator) return null;
|
|
9194
|
+
if (typeof decorator === "function") return decorator(this, editor);
|
|
9195
|
+
return decorator.render(this, editor);
|
|
7250
9196
|
}
|
|
7251
9197
|
};
|
|
9198
|
+
function $createSchemaNode(payload) {
|
|
9199
|
+
return $applyNodeReplacement(new SchemaNode(payload.url, payload.schemaType, payload.payload, payload.title));
|
|
9200
|
+
}
|
|
9201
|
+
function $convertSchemaElement(domNode) {
|
|
9202
|
+
const element = domNode;
|
|
9203
|
+
return { node: $createSchemaNode({
|
|
9204
|
+
payload: parsePayload(element.dataset.payload),
|
|
9205
|
+
schemaType: element.dataset.schemaType,
|
|
9206
|
+
title: element.textContent || element.href,
|
|
9207
|
+
url: element.getAttribute("href") || ""
|
|
9208
|
+
}) };
|
|
9209
|
+
}
|
|
9210
|
+
function parsePayload(value) {
|
|
9211
|
+
if (!value) return void 0;
|
|
9212
|
+
try {
|
|
9213
|
+
return JSON.parse(value);
|
|
9214
|
+
} catch {
|
|
9215
|
+
return value;
|
|
9216
|
+
}
|
|
9217
|
+
}
|
|
9218
|
+
function $isSchemaNode(node) {
|
|
9219
|
+
return node instanceof SchemaNode || node?.getType?.() === SchemaNode.getType();
|
|
9220
|
+
}
|
|
9221
|
+
//#endregion
|
|
9222
|
+
//#region src/plugins/link/normalization/index.ts
|
|
9223
|
+
function normalizeSchemaLinkNode(node, editor, linkService) {
|
|
9224
|
+
const url = node.getURL();
|
|
9225
|
+
const title = node.getTitle() || node.getTextContent() || url;
|
|
9226
|
+
const schema = linkService.parseSchemaUrl(url);
|
|
9227
|
+
const rule = linkService.getSchemaRule(url, {
|
|
9228
|
+
editor,
|
|
9229
|
+
schema,
|
|
9230
|
+
text: node.getTextContent(),
|
|
9231
|
+
title
|
|
9232
|
+
});
|
|
9233
|
+
if (!rule) return false;
|
|
9234
|
+
const parsed = rule.parse?.(url, schema);
|
|
9235
|
+
const payload = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? normalizeSchemaPayload(parsed) : { payload: parsed };
|
|
9236
|
+
if (linkService.hasSchemaLinkRenderer(url)) payload.payload = {
|
|
9237
|
+
...payload.payload && typeof payload.payload === "object" ? payload.payload : {},
|
|
9238
|
+
__legacyLink: {
|
|
9239
|
+
rel: node.getRel(),
|
|
9240
|
+
target: node.getTarget(),
|
|
9241
|
+
text: node.getTextContent(),
|
|
9242
|
+
title: node.getTitle()
|
|
9243
|
+
}
|
|
9244
|
+
};
|
|
9245
|
+
node.replace($createSchemaNode({
|
|
9246
|
+
payload: payload.payload,
|
|
9247
|
+
schemaType: payload.schemaType || rule.id,
|
|
9248
|
+
title: payload.title || title,
|
|
9249
|
+
url: payload.url || url
|
|
9250
|
+
}));
|
|
9251
|
+
return true;
|
|
9252
|
+
}
|
|
9253
|
+
function normalizeSchemaPayload(payload) {
|
|
9254
|
+
if ("payload" in payload || "schemaType" in payload || "title" in payload || "url" in payload) return payload;
|
|
9255
|
+
return { payload };
|
|
9256
|
+
}
|
|
7252
9257
|
//#endregion
|
|
7253
9258
|
//#region src/plugins/link/utils/index.ts
|
|
7254
9259
|
const SUPPORTED_URL_PROTOCOLS = /* @__PURE__ */ new Set([
|
|
@@ -7377,10 +9382,22 @@ const LinkPlugin = class extends KernelPlugin {
|
|
|
7377
9382
|
super();
|
|
7378
9383
|
this.kernel = kernel;
|
|
7379
9384
|
this.config = config;
|
|
7380
|
-
this.linkRegex = /^
|
|
9385
|
+
this.linkRegex = /^[a-z][\d+.a-z-]*:\/\/\S+$/i;
|
|
7381
9386
|
this.service = new LinkService();
|
|
7382
|
-
kernel.registerNodes([
|
|
9387
|
+
kernel.registerNodes([
|
|
9388
|
+
LinkNode,
|
|
9389
|
+
AutoLinkNode,
|
|
9390
|
+
LinkCardNode,
|
|
9391
|
+
LinkBlockCardNode,
|
|
9392
|
+
LinkIframeNode,
|
|
9393
|
+
SchemaNode
|
|
9394
|
+
]);
|
|
9395
|
+
this.service.updateConfig(config);
|
|
7383
9396
|
kernel.registerService(ILinkService, this.service);
|
|
9397
|
+
this.registerDecorator(kernel, LinkCardNode.getType(), (node, editor) => config?.decoratorCard ? config.decoratorCard(node, editor) : null);
|
|
9398
|
+
this.registerDecorator(kernel, LinkBlockCardNode.getType(), (node, editor) => config?.decoratorCard ? config.decoratorCard(node, editor) : null);
|
|
9399
|
+
this.registerDecorator(kernel, LinkIframeNode.getType(), (node, editor) => config?.decoratorIframe ? config.decoratorIframe(node, editor) : null);
|
|
9400
|
+
this.registerDecorator(kernel, SchemaNode.getType(), (node, editor) => config?.decoratorSchema ? config.decoratorSchema(node, editor) : null);
|
|
7384
9401
|
if (config?.theme) kernel.registerThemes(config.theme);
|
|
7385
9402
|
if (config?.linkRegex) this.linkRegex = config.linkRegex;
|
|
7386
9403
|
}
|
|
@@ -7404,6 +9421,9 @@ const LinkPlugin = class extends KernelPlugin {
|
|
|
7404
9421
|
}
|
|
7405
9422
|
return false;
|
|
7406
9423
|
}, COMMAND_PRIORITY_NORMAL));
|
|
9424
|
+
if (this.config?.normalizeSchemaLinks !== false) this.register(editor.registerNodeTransform(LinkNode, (node) => {
|
|
9425
|
+
normalizeSchemaLinkNode(node, editor, this.service);
|
|
9426
|
+
}));
|
|
7407
9427
|
this.registerMarkdown();
|
|
7408
9428
|
this.registerLiteXml();
|
|
7409
9429
|
}
|
|
@@ -7428,6 +9448,74 @@ const LinkPlugin = class extends KernelPlugin {
|
|
|
7428
9448
|
version: 1
|
|
7429
9449
|
})];
|
|
7430
9450
|
});
|
|
9451
|
+
litexmlService.registerXMLWriter(LinkCardNode.getType(), (node, ctx) => {
|
|
9452
|
+
if (!$isLinkCardNode(node)) return false;
|
|
9453
|
+
return ctx.createXmlNode("link-card", {
|
|
9454
|
+
description: node.getDescription(),
|
|
9455
|
+
href: node.getURL(),
|
|
9456
|
+
icon: node.getIcon(),
|
|
9457
|
+
openTarget: node.getOpenTarget() || "_blank",
|
|
9458
|
+
title: node.getTitle()
|
|
9459
|
+
});
|
|
9460
|
+
});
|
|
9461
|
+
litexmlService.registerXMLWriter(LinkBlockCardNode.getType(), (node, ctx) => {
|
|
9462
|
+
if (!$isLinkBlockCardNode(node)) return false;
|
|
9463
|
+
return ctx.createXmlNode("link-card", {
|
|
9464
|
+
description: node.getDescription(),
|
|
9465
|
+
href: node.getURL(),
|
|
9466
|
+
icon: node.getIcon(),
|
|
9467
|
+
layout: "block",
|
|
9468
|
+
openTarget: node.getOpenTarget() || "_blank",
|
|
9469
|
+
title: node.getTitle()
|
|
9470
|
+
});
|
|
9471
|
+
});
|
|
9472
|
+
litexmlService.registerXMLReader("link-card", (xmlNode) => {
|
|
9473
|
+
return [INodeHelper.createTypeNode(xmlNode.getAttribute("layout") === "block" ? LinkBlockCardNode.getType() : LinkCardNode.getType(), {
|
|
9474
|
+
description: xmlNode.getAttribute("description") || "",
|
|
9475
|
+
icon: xmlNode.getAttribute("icon") || "",
|
|
9476
|
+
openTarget: xmlNode.getAttribute("openTarget") || "_blank",
|
|
9477
|
+
title: xmlNode.getAttribute("title") || xmlNode.getAttribute("href") || "",
|
|
9478
|
+
type: xmlNode.getAttribute("layout") === "block" ? LinkBlockCardNode.getType() : LinkCardNode.getType(),
|
|
9479
|
+
url: xmlNode.getAttribute("href") || "",
|
|
9480
|
+
version: 1
|
|
9481
|
+
})];
|
|
9482
|
+
});
|
|
9483
|
+
litexmlService.registerXMLWriter(LinkIframeNode.getType(), (node, ctx) => {
|
|
9484
|
+
if (!$isLinkIframeNode(node)) return false;
|
|
9485
|
+
return ctx.createXmlNode("link-iframe", {
|
|
9486
|
+
href: node.getURL(),
|
|
9487
|
+
src: node.getSrc(),
|
|
9488
|
+
title: node.getTitle()
|
|
9489
|
+
});
|
|
9490
|
+
});
|
|
9491
|
+
litexmlService.registerXMLReader("link-iframe", (xmlNode) => {
|
|
9492
|
+
return [INodeHelper.createTypeNode(LinkIframeNode.getType(), {
|
|
9493
|
+
src: xmlNode.getAttribute("src") || xmlNode.getAttribute("href") || "",
|
|
9494
|
+
title: xmlNode.getAttribute("title") || xmlNode.getAttribute("href") || "",
|
|
9495
|
+
type: LinkIframeNode.getType(),
|
|
9496
|
+
url: xmlNode.getAttribute("href") || "",
|
|
9497
|
+
version: 1
|
|
9498
|
+
})];
|
|
9499
|
+
});
|
|
9500
|
+
litexmlService.registerXMLWriter(SchemaNode.getType(), (node, ctx) => {
|
|
9501
|
+
if (!$isSchemaNode(node)) return false;
|
|
9502
|
+
return ctx.createXmlNode("schema-link", {
|
|
9503
|
+
href: node.getURL(),
|
|
9504
|
+
payload: JSON.stringify(node.getPayload() ?? null),
|
|
9505
|
+
schemaType: node.getSchemaType(),
|
|
9506
|
+
title: node.getTitle()
|
|
9507
|
+
});
|
|
9508
|
+
});
|
|
9509
|
+
litexmlService.registerXMLReader("schema-link", (xmlNode) => {
|
|
9510
|
+
return [INodeHelper.createTypeNode(SchemaNode.getType(), {
|
|
9511
|
+
payload: parseJsonAttribute(xmlNode.getAttribute("payload")),
|
|
9512
|
+
schemaType: xmlNode.getAttribute("schemaType") || "",
|
|
9513
|
+
title: xmlNode.getAttribute("title") || xmlNode.getAttribute("href") || "",
|
|
9514
|
+
type: SchemaNode.getType(),
|
|
9515
|
+
url: xmlNode.getAttribute("href") || "",
|
|
9516
|
+
version: 1
|
|
9517
|
+
})];
|
|
9518
|
+
});
|
|
7431
9519
|
}
|
|
7432
9520
|
registerMarkdown() {
|
|
7433
9521
|
this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownShortCut({
|
|
@@ -7447,6 +9535,30 @@ const LinkPlugin = class extends KernelPlugin {
|
|
|
7447
9535
|
this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownWriter(LinkNode.getType(), (ctx, node) => {
|
|
7448
9536
|
if ($isLinkNode(node)) ctx.wrap("[", `](${node.getURL()})`);
|
|
7449
9537
|
});
|
|
9538
|
+
this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownWriter(LinkCardNode.getType(), (ctx, node) => {
|
|
9539
|
+
if ($isLinkCardNode(node)) {
|
|
9540
|
+
ctx.appendLine(`[${node.getTitle()}](${node.getURL()})\n`);
|
|
9541
|
+
return true;
|
|
9542
|
+
}
|
|
9543
|
+
});
|
|
9544
|
+
this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownWriter(LinkBlockCardNode.getType(), (ctx, node) => {
|
|
9545
|
+
if ($isLinkBlockCardNode(node)) {
|
|
9546
|
+
ctx.appendLine(`[${node.getTitle()}](${node.getURL()})\n`);
|
|
9547
|
+
return true;
|
|
9548
|
+
}
|
|
9549
|
+
});
|
|
9550
|
+
this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownWriter(LinkIframeNode.getType(), (ctx, node) => {
|
|
9551
|
+
if ($isLinkIframeNode(node)) {
|
|
9552
|
+
ctx.appendLine(`[${node.getTitle()}](${node.getURL()})\n`);
|
|
9553
|
+
return true;
|
|
9554
|
+
}
|
|
9555
|
+
});
|
|
9556
|
+
this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownWriter(SchemaNode.getType(), (ctx, node) => {
|
|
9557
|
+
if ($isSchemaNode(node)) {
|
|
9558
|
+
ctx.appendLine(`[${node.getTitle()}](${node.getURL()})\n`);
|
|
9559
|
+
return true;
|
|
9560
|
+
}
|
|
9561
|
+
});
|
|
7450
9562
|
this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownReader("link", (node, children) => {
|
|
7451
9563
|
return [INodeHelper.createElementNode("link", {
|
|
7452
9564
|
children,
|
|
@@ -7461,6 +9573,14 @@ const LinkPlugin = class extends KernelPlugin {
|
|
|
7461
9573
|
});
|
|
7462
9574
|
}
|
|
7463
9575
|
};
|
|
9576
|
+
function parseJsonAttribute(value) {
|
|
9577
|
+
if (!value) return void 0;
|
|
9578
|
+
try {
|
|
9579
|
+
return JSON.parse(value);
|
|
9580
|
+
} catch {
|
|
9581
|
+
return value;
|
|
9582
|
+
}
|
|
9583
|
+
}
|
|
7464
9584
|
//#endregion
|
|
7465
9585
|
//#region src/utils/cx.ts
|
|
7466
9586
|
const cx = (...classNames) => classNames.filter(Boolean).join(" ");
|
|
@@ -7819,7 +9939,11 @@ const ListPlugin = class extends KernelPlugin {
|
|
|
7819
9939
|
type: "element"
|
|
7820
9940
|
});
|
|
7821
9941
|
markdownService.registerMarkdownWriter(ListNode.getType(), (ctx, node) => {
|
|
7822
|
-
if ($isListNode(node))
|
|
9942
|
+
if ($isListNode(node)) {
|
|
9943
|
+
const parent = node.getParent();
|
|
9944
|
+
const needsLeadingNewline = $isListItemNode(parent) && node.getPreviousSibling() !== null;
|
|
9945
|
+
ctx.wrap(needsLeadingNewline ? "\n" : "", "\n");
|
|
9946
|
+
}
|
|
7823
9947
|
});
|
|
7824
9948
|
const getLevel = (node) => {
|
|
7825
9949
|
if (!node) return 0;
|
|
@@ -7850,7 +9974,10 @@ const ListPlugin = class extends KernelPlugin {
|
|
|
7850
9974
|
let start = node.start || 1;
|
|
7851
9975
|
return INodeHelper.createElementNode("list", {
|
|
7852
9976
|
children: children.map((v) => {
|
|
7853
|
-
if (v.type === "listitem")
|
|
9977
|
+
if (v.type === "listitem") {
|
|
9978
|
+
v.value = start;
|
|
9979
|
+
if (v.children?.[0]?.type !== "list") start++;
|
|
9980
|
+
}
|
|
7854
9981
|
return v;
|
|
7855
9982
|
}),
|
|
7856
9983
|
direction: "ltr",
|
|
@@ -8922,10 +11049,88 @@ var TableControllerMenuService = class {
|
|
|
8922
11049
|
}
|
|
8923
11050
|
};
|
|
8924
11051
|
//#endregion
|
|
11052
|
+
//#region src/plugins/table/plugin/single-cell-paste.ts
|
|
11053
|
+
const LEXICAL_CLIPBOARD_TYPE = "application/x-lexical-editor";
|
|
11054
|
+
const countNodesByType = (node, type) => {
|
|
11055
|
+
return (node.type === type ? 1 : 0) + (Array.isArray(node.children) ? node.children.reduce((count, child) => count + countNodesByType(child, type), 0) : 0);
|
|
11056
|
+
};
|
|
11057
|
+
const parseLexicalTableClipboard = (serializedClipboard) => {
|
|
11058
|
+
if (!serializedClipboard) return void 0;
|
|
11059
|
+
try {
|
|
11060
|
+
const clipboard = JSON.parse(serializedClipboard);
|
|
11061
|
+
if (!Array.isArray(clipboard.nodes) || clipboard.nodes.length !== 1) return { isSingleCell: false };
|
|
11062
|
+
const [rootNode] = clipboard.nodes;
|
|
11063
|
+
if (rootNode?.type !== "table") return { isSingleCell: false };
|
|
11064
|
+
const cells = [];
|
|
11065
|
+
const collectCells = (node) => {
|
|
11066
|
+
if (node.type === "tablecell") cells.push(node);
|
|
11067
|
+
node.children?.forEach(collectCells);
|
|
11068
|
+
};
|
|
11069
|
+
collectCells(rootNode);
|
|
11070
|
+
const isSingleCell = countNodesByType(rootNode, "table") === 1 && cells.length === 1;
|
|
11071
|
+
return {
|
|
11072
|
+
cell: isSingleCell ? cells[0] : void 0,
|
|
11073
|
+
isSingleCell
|
|
11074
|
+
};
|
|
11075
|
+
} catch {
|
|
11076
|
+
return;
|
|
11077
|
+
}
|
|
11078
|
+
};
|
|
11079
|
+
const getSerializedNodeText = (node) => {
|
|
11080
|
+
if (node.type === "text") return node.text || "";
|
|
11081
|
+
if (node.type === "linebreak") return "\n";
|
|
11082
|
+
if (node.type === "tab") return " ";
|
|
11083
|
+
return node.children?.map(getSerializedNodeText).join("") || "";
|
|
11084
|
+
};
|
|
11085
|
+
const getSerializedCellText = (cell) => {
|
|
11086
|
+
return cell.children?.map(getSerializedNodeText).join("\n") || "";
|
|
11087
|
+
};
|
|
11088
|
+
const getSingleCellHTMLText = (html) => {
|
|
11089
|
+
if (!isSingleCellHTMLTable(html) || typeof DOMParser === "undefined") return void 0;
|
|
11090
|
+
const cell = new DOMParser().parseFromString(html, "text/html").body.querySelector("td, th");
|
|
11091
|
+
if (!cell) return void 0;
|
|
11092
|
+
cell.querySelectorAll("br").forEach((node) => node.replaceWith("\n"));
|
|
11093
|
+
return Array.from(cell.children).map((node) => node.textContent || "").join("\n") || cell.textContent || "";
|
|
11094
|
+
};
|
|
11095
|
+
const isSingleCellHTMLTable = (html) => {
|
|
11096
|
+
if (!html || typeof DOMParser === "undefined") return false;
|
|
11097
|
+
const document = new DOMParser().parseFromString(html, "text/html");
|
|
11098
|
+
const tables = document.body.querySelectorAll("table");
|
|
11099
|
+
if (tables.length !== 1 || tables[0].querySelectorAll("td, th").length !== 1) return false;
|
|
11100
|
+
const remainder = document.body.cloneNode(true);
|
|
11101
|
+
remainder.querySelector("table")?.remove();
|
|
11102
|
+
remainder.querySelectorAll("link, meta, script, style").forEach((node) => node.remove());
|
|
11103
|
+
return !remainder.querySelector("audio, canvas, iframe, img, object, svg, video") && remainder.textContent?.trim() === "";
|
|
11104
|
+
};
|
|
11105
|
+
const isSingleCellTableClipboard = (clipboardData) => {
|
|
11106
|
+
const lexicalResult = parseLexicalTableClipboard(clipboardData.getData(LEXICAL_CLIPBOARD_TYPE));
|
|
11107
|
+
if (lexicalResult !== void 0) return lexicalResult.isSingleCell;
|
|
11108
|
+
return isSingleCellHTMLTable(clipboardData.getData("text/html"));
|
|
11109
|
+
};
|
|
11110
|
+
const getSingleCellPlainText = (clipboardData) => {
|
|
11111
|
+
const lexicalResult = parseLexicalTableClipboard(clipboardData.getData(LEXICAL_CLIPBOARD_TYPE));
|
|
11112
|
+
if (lexicalResult?.cell) return getSerializedCellText(lexicalResult.cell);
|
|
11113
|
+
const htmlText = getSingleCellHTMLText(clipboardData.getData("text/html"));
|
|
11114
|
+
if (htmlText !== void 0) return htmlText;
|
|
11115
|
+
return clipboardData.getData("text/plain").replace(/(?:\r\n|\n)$/, "");
|
|
11116
|
+
};
|
|
11117
|
+
const handleSingleCellTablePaste = (editor, event) => {
|
|
11118
|
+
if (!("clipboardData" in event)) return false;
|
|
11119
|
+
const clipboardData = event.clipboardData;
|
|
11120
|
+
if (!clipboardData || !isSingleCellTableClipboard(clipboardData)) return false;
|
|
11121
|
+
event.preventDefault();
|
|
11122
|
+
editor.dispatchCommand(CONTROLLED_TEXT_INSERTION_COMMAND, getSingleCellPlainText(clipboardData));
|
|
11123
|
+
return true;
|
|
11124
|
+
};
|
|
11125
|
+
const registerSingleCellTablePaste = (editor) => {
|
|
11126
|
+
return editor.registerCommand(PASTE_COMMAND, (event) => handleSingleCellTablePaste(editor, event), COMMAND_PRIORITY_HIGH);
|
|
11127
|
+
};
|
|
11128
|
+
//#endregion
|
|
8925
11129
|
//#region src/plugins/table/plugin/index.ts
|
|
8926
11130
|
const tableCellProcessor = (before, content, after) => {
|
|
8927
11131
|
return before + content.replace(/\n+$/, "").replaceAll(/\n+/g, "<br />") + after;
|
|
8928
11132
|
};
|
|
11133
|
+
const isRemovedTableRowDiff = (node) => node.getType() === "table-row-diff" && "getDiffType" in node && typeof node.getDiffType === "function" && node.getDiffType() === "remove";
|
|
8929
11134
|
function isHeadlessEditor(editor) {
|
|
8930
11135
|
return editor._headless === true;
|
|
8931
11136
|
}
|
|
@@ -8974,6 +11179,7 @@ const TablePlugin = class extends KernelPlugin {
|
|
|
8974
11179
|
}
|
|
8975
11180
|
onInit(editor) {
|
|
8976
11181
|
this.register(registerTableCellUnmergeTransform(editor));
|
|
11182
|
+
this.register(registerSingleCellTablePaste(editor));
|
|
8977
11183
|
if (!isHeadlessEditor(editor)) {
|
|
8978
11184
|
setScrollableTablesActive(editor, true);
|
|
8979
11185
|
this.register(registerTablePlugin(editor));
|
|
@@ -9220,7 +11426,7 @@ const TablePlugin = class extends KernelPlugin {
|
|
|
9220
11426
|
markdownService.registerMarkdownWriter(TableRowNode.getType(), (ctx, node) => {
|
|
9221
11427
|
const parent = node.getParent();
|
|
9222
11428
|
if (!$isTableNode(parent)) return;
|
|
9223
|
-
if (!node.
|
|
11429
|
+
if (!node.getPreviousSiblings().some((sibling) => !isRemovedTableRowDiff(sibling))) ctx.wrap("", `\n${parent.getColWidths()?.map(() => {
|
|
9224
11430
|
return "|:--";
|
|
9225
11431
|
}).join("")}|\n`);
|
|
9226
11432
|
else ctx.wrap("", "\n");
|
|
@@ -9267,6 +11473,207 @@ const TablePlugin = class extends KernelPlugin {
|
|
|
9267
11473
|
}
|
|
9268
11474
|
};
|
|
9269
11475
|
//#endregion
|
|
11476
|
+
//#region src/plugins/collapsible/node/CollapsibleNode.ts
|
|
11477
|
+
const CONTENT_SELECTOR = "[data-collapsible-content=\"true\"]";
|
|
11478
|
+
const TOGGLE_SELECTOR = "[data-collapsible-toggle=\"true\"]";
|
|
11479
|
+
var CollapsibleNode = class CollapsibleNode extends ElementNode {
|
|
11480
|
+
static getType() {
|
|
11481
|
+
return "collapsible";
|
|
11482
|
+
}
|
|
11483
|
+
static clone(node) {
|
|
11484
|
+
return new CollapsibleNode(node.__title, node.__collapsed, node.__key);
|
|
11485
|
+
}
|
|
11486
|
+
static importDOM() {
|
|
11487
|
+
return { details: () => ({
|
|
11488
|
+
conversion: $convertDetailsElement,
|
|
11489
|
+
priority: 1
|
|
11490
|
+
}) };
|
|
11491
|
+
}
|
|
11492
|
+
static importJSON(serializedNode) {
|
|
11493
|
+
const node = $createCollapsibleNode(serializedNode.title || "Details").updateFromJSON(serializedNode);
|
|
11494
|
+
if (shouldPrependTitle(serializedNode)) {
|
|
11495
|
+
const titleParagraph = $createTitleParagraph(serializedNode.title || "Details");
|
|
11496
|
+
const firstChild = node.getFirstChild();
|
|
11497
|
+
if (firstChild) firstChild.insertBefore(titleParagraph);
|
|
11498
|
+
else node.append(titleParagraph);
|
|
11499
|
+
}
|
|
11500
|
+
return node;
|
|
11501
|
+
}
|
|
11502
|
+
constructor(title = "Details", collapsed = false, key) {
|
|
11503
|
+
super(key);
|
|
11504
|
+
this.__title = title;
|
|
11505
|
+
this.__collapsed = collapsed;
|
|
11506
|
+
}
|
|
11507
|
+
createDOM(config, editor) {
|
|
11508
|
+
const element = document.createElement("section");
|
|
11509
|
+
addClassNamesToElement(element, config.theme.collapsible);
|
|
11510
|
+
element.dataset.collapsible = "true";
|
|
11511
|
+
element.dataset.collapsibleCollapsed = String(this.__collapsed);
|
|
11512
|
+
const toggle = document.createElement("button");
|
|
11513
|
+
toggle.type = "button";
|
|
11514
|
+
toggle.contentEditable = "false";
|
|
11515
|
+
toggle.dataset.collapsibleToggle = "true";
|
|
11516
|
+
toggle.setAttribute("aria-expanded", String(!this.__collapsed));
|
|
11517
|
+
toggle.setAttribute("aria-label", this.__collapsed ? "Expand collapsible block" : "Collapse collapsible block");
|
|
11518
|
+
toggle.addEventListener("click", () => {
|
|
11519
|
+
editor.update(() => {
|
|
11520
|
+
const latest = this.getLatest();
|
|
11521
|
+
if ($isCollapsibleNode(latest)) latest.setCollapsed(!latest.isCollapsed());
|
|
11522
|
+
});
|
|
11523
|
+
});
|
|
11524
|
+
const content = document.createElement("div");
|
|
11525
|
+
content.dataset.collapsibleContent = "true";
|
|
11526
|
+
element.append(toggle, content);
|
|
11527
|
+
return element;
|
|
11528
|
+
}
|
|
11529
|
+
updateDOM(prevNode, dom) {
|
|
11530
|
+
const toggle = dom.querySelector(TOGGLE_SELECTOR);
|
|
11531
|
+
dom.dataset.collapsibleCollapsed = String(this.__collapsed);
|
|
11532
|
+
if (toggle && prevNode.__collapsed !== this.__collapsed) {
|
|
11533
|
+
toggle.setAttribute("aria-expanded", String(!this.__collapsed));
|
|
11534
|
+
toggle.setAttribute("aria-label", this.__collapsed ? "Expand collapsible block" : "Collapse collapsible block");
|
|
11535
|
+
}
|
|
11536
|
+
return false;
|
|
11537
|
+
}
|
|
11538
|
+
exportDOM() {
|
|
11539
|
+
const element = document.createElement("details");
|
|
11540
|
+
if (!this.__collapsed) element.setAttribute("open", "");
|
|
11541
|
+
const summary = document.createElement("summary");
|
|
11542
|
+
summary.textContent = this.getTitle();
|
|
11543
|
+
element.append(summary);
|
|
11544
|
+
return { element };
|
|
11545
|
+
}
|
|
11546
|
+
exportJSON() {
|
|
11547
|
+
return {
|
|
11548
|
+
...super.exportJSON(),
|
|
11549
|
+
collapsed: this.isCollapsed(),
|
|
11550
|
+
title: this.getTitle()
|
|
11551
|
+
};
|
|
11552
|
+
}
|
|
11553
|
+
getDOMSlot(element) {
|
|
11554
|
+
const content = element.querySelector(CONTENT_SELECTOR);
|
|
11555
|
+
if (content instanceof HTMLElement) return super.getDOMSlot(element).withElement(content);
|
|
11556
|
+
return super.getDOMSlot(element);
|
|
11557
|
+
}
|
|
11558
|
+
getTitle() {
|
|
11559
|
+
const latest = this.getLatest();
|
|
11560
|
+
const firstChild = latest.getFirstChild();
|
|
11561
|
+
return firstChild ? firstChild.getTextContent().trim() : latest.__title;
|
|
11562
|
+
}
|
|
11563
|
+
isCollapsed() {
|
|
11564
|
+
return this.getLatest().__collapsed;
|
|
11565
|
+
}
|
|
11566
|
+
isShadowRoot() {
|
|
11567
|
+
return true;
|
|
11568
|
+
}
|
|
11569
|
+
setCollapsed(collapsed) {
|
|
11570
|
+
const writable = this.getWritable();
|
|
11571
|
+
writable.__collapsed = collapsed;
|
|
11572
|
+
return writable;
|
|
11573
|
+
}
|
|
11574
|
+
setTitle(title) {
|
|
11575
|
+
const writable = this.getWritable();
|
|
11576
|
+
writable.__title = title;
|
|
11577
|
+
return writable;
|
|
11578
|
+
}
|
|
11579
|
+
updateFromJSON(serializedNode) {
|
|
11580
|
+
return super.updateFromJSON(serializedNode).setTitle(serializedNode.title || "Details").setCollapsed(Boolean(serializedNode.collapsed));
|
|
11581
|
+
}
|
|
11582
|
+
};
|
|
11583
|
+
function $createCollapsibleNode(title = "Details", collapsed = false) {
|
|
11584
|
+
return $applyNodeReplacement(new CollapsibleNode(title, collapsed));
|
|
11585
|
+
}
|
|
11586
|
+
function $isCollapsibleNode(node) {
|
|
11587
|
+
return node instanceof CollapsibleNode;
|
|
11588
|
+
}
|
|
11589
|
+
function $convertDetailsElement(domNode) {
|
|
11590
|
+
if (!(domNode instanceof HTMLDetailsElement)) return { node: null };
|
|
11591
|
+
return { node: $createCollapsibleNode(domNode.querySelector("summary")?.textContent?.trim() || "Details", !domNode.open) };
|
|
11592
|
+
}
|
|
11593
|
+
function $createTitleParagraph(title) {
|
|
11594
|
+
const paragraph = $createParagraphNode();
|
|
11595
|
+
paragraph.append($createTextNode(title));
|
|
11596
|
+
return paragraph;
|
|
11597
|
+
}
|
|
11598
|
+
function getSerializedText(node) {
|
|
11599
|
+
if (!node || typeof node !== "object") return "";
|
|
11600
|
+
if ("text" in node && typeof node.text === "string") return node.text;
|
|
11601
|
+
if ("children" in node && Array.isArray(node.children)) return node.children.map((child) => getSerializedText(child)).join("");
|
|
11602
|
+
return "";
|
|
11603
|
+
}
|
|
11604
|
+
function shouldPrependTitle(serializedNode) {
|
|
11605
|
+
const title = serializedNode.title?.trim();
|
|
11606
|
+
if (!title) return false;
|
|
11607
|
+
const firstChild = (Array.isArray(serializedNode.children) ? serializedNode.children : [])[0];
|
|
11608
|
+
return getSerializedText(firstChild).trim() !== title;
|
|
11609
|
+
}
|
|
11610
|
+
//#endregion
|
|
11611
|
+
//#region src/headless/collapsible-plugin.ts
|
|
11612
|
+
const HeadlessCollapsiblePlugin = class extends KernelPlugin {
|
|
11613
|
+
static {
|
|
11614
|
+
this.pluginName = "HeadlessCollapsiblePlugin";
|
|
11615
|
+
}
|
|
11616
|
+
constructor(kernel) {
|
|
11617
|
+
super();
|
|
11618
|
+
this.kernel = kernel;
|
|
11619
|
+
kernel.registerNodes([CollapsibleNode]);
|
|
11620
|
+
}
|
|
11621
|
+
onInit(_editor) {
|
|
11622
|
+
const markdownService = this.kernel.requireService(IMarkdownShortCutService);
|
|
11623
|
+
markdownService?.registerMarkdownWriter(CollapsibleNode.getType(), (ctx, node) => {
|
|
11624
|
+
if (!$isCollapsibleNode(node)) return false;
|
|
11625
|
+
ctx.appendLine(`<details${node.isCollapsed() ? "" : " open"}>\n`);
|
|
11626
|
+
ctx.appendLine(`<summary>${escapeHtml(node.getTitle())}</summary>\n\n`);
|
|
11627
|
+
node.getChildren().slice(1).forEach((child) => ctx.processChild(ctx, child));
|
|
11628
|
+
ctx.appendLine("\n</details>\n");
|
|
11629
|
+
return true;
|
|
11630
|
+
});
|
|
11631
|
+
markdownService?.registerMarkdownReader("html", (node, children) => {
|
|
11632
|
+
const tag = getHtmlTagName(node.value);
|
|
11633
|
+
if (tag === "summary") return INodeHelper.createTypeNode("__collapsible_summary", { text: getNodeText(children) });
|
|
11634
|
+
if (tag !== "details") return false;
|
|
11635
|
+
const summaryNode = children.find((child) => child.type === "__collapsible_summary");
|
|
11636
|
+
const contentChildren = children.filter((child) => child.type !== "__collapsible_summary");
|
|
11637
|
+
const title = summaryNode && "text" in summaryNode && typeof summaryNode.text === "string" ? summaryNode.text : extractSummaryTitle(node.value) || "Details";
|
|
11638
|
+
return INodeHelper.createElementNode(CollapsibleNode.getType(), {
|
|
11639
|
+
children: ensureTitleChildren(contentChildren, title),
|
|
11640
|
+
collapsed: !/\sopen(?:\s|>|$)/i.test(node.value),
|
|
11641
|
+
title
|
|
11642
|
+
});
|
|
11643
|
+
});
|
|
11644
|
+
const litexmlService = this.kernel.requireService(ILitexmlService);
|
|
11645
|
+
litexmlService?.registerXMLWriter(CollapsibleNode.getType(), (node, ctx) => {
|
|
11646
|
+
if (!$isCollapsibleNode(node)) return false;
|
|
11647
|
+
return ctx.createXmlNode("collapsible", {
|
|
11648
|
+
collapsed: String(node.isCollapsed()),
|
|
11649
|
+
title: node.getTitle()
|
|
11650
|
+
});
|
|
11651
|
+
});
|
|
11652
|
+
litexmlService?.registerXMLReader("collapsible", (xmlNode, children) => {
|
|
11653
|
+
const title = xmlNode.getAttribute("title") || "Details";
|
|
11654
|
+
return INodeHelper.createElementNode(CollapsibleNode.getType(), {
|
|
11655
|
+
children: ensureTitleChildren(children, title),
|
|
11656
|
+
collapsed: xmlNode.getAttribute("collapsed") === "true",
|
|
11657
|
+
title
|
|
11658
|
+
});
|
|
11659
|
+
});
|
|
11660
|
+
}
|
|
11661
|
+
};
|
|
11662
|
+
const getHtmlTagName = (value) => value.match(/^<\/?\s*([\da-z-]+)/i)?.[1]?.toLowerCase() || "";
|
|
11663
|
+
const escapeHtml = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """);
|
|
11664
|
+
const extractSummaryTitle = (value) => value.match(/<summary\b[^>]*>([\S\s]*?)<\/summary>/i)?.[1]?.trim() || "";
|
|
11665
|
+
const getNodeText = (children) => children.map((child) => {
|
|
11666
|
+
if (!child || typeof child !== "object") return "";
|
|
11667
|
+
if ("text" in child && typeof child.text === "string") return child.text;
|
|
11668
|
+
if ("children" in child && Array.isArray(child.children)) return getNodeText(child.children);
|
|
11669
|
+
return "";
|
|
11670
|
+
}).join("");
|
|
11671
|
+
const ensureTitleChildren = (children, title) => {
|
|
11672
|
+
const trimmedTitle = title.trim();
|
|
11673
|
+
if (!trimmedTitle || getNodeText(children.slice(0, 1)).trim() === trimmedTitle) return children;
|
|
11674
|
+
return [INodeHelper.createParagraph({ children: [INodeHelper.createTextNode(title)] }), ...children];
|
|
11675
|
+
};
|
|
11676
|
+
//#endregion
|
|
9270
11677
|
//#region src/headless/extract-media-from-editor-state.ts
|
|
9271
11678
|
const IMAGE_TYPES = /* @__PURE__ */ new Set(["image", "block-image"]);
|
|
9272
11679
|
const FILE_TYPE = "file";
|
|
@@ -9416,6 +11823,7 @@ const DEFAULT_HEADLESS_EDITOR_PLUGINS = [
|
|
|
9416
11823
|
HRPlugin,
|
|
9417
11824
|
ListPlugin,
|
|
9418
11825
|
TablePlugin,
|
|
11826
|
+
HeadlessCollapsiblePlugin,
|
|
9419
11827
|
LitexmlPlugin
|
|
9420
11828
|
];
|
|
9421
11829
|
var HeadlessEditor = class {
|