@lobehub/editor 4.24.1 → 4.25.1
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.d.ts +9 -0
- package/es/editor-kernel/kernel.js +74 -11
- 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 +30 -0
- package/es/headless.js +2618 -139
- 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 +14 -4
- package/es/plugins/common/plugin/progressive-select-all.js +81 -0
- package/es/plugins/common/plugin/register.js +73 -4
- package/es/plugins/common/react/ReactPlainText.js +27 -9
- 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/react/hooks/useEditor.d.ts +24 -2
- package/es/react/hooks/useEditor.js +13 -3
- package/es/react.d.ts +2 -2
- package/es/renderer/LexicalRenderer.js +9 -2
- package/es/types/kernel.d.ts +16 -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
|
|
@@ -804,26 +818,46 @@ var Kernel = class Kernel extends EventEmitter {
|
|
|
804
818
|
return this.editor || null;
|
|
805
819
|
}
|
|
806
820
|
destroy() {
|
|
807
|
-
|
|
821
|
+
const editor = this.editor;
|
|
822
|
+
const editorId = this.getEditorId();
|
|
808
823
|
this.logger.info(`🗑️ Destroying editor with ${this.pluginsInstances.length} plugins`);
|
|
824
|
+
if (editor) try {
|
|
825
|
+
editor.setRootElement(null);
|
|
826
|
+
} catch (error) {
|
|
827
|
+
this.logger.warn("Failed to detach editor root during destroy:", error);
|
|
828
|
+
}
|
|
829
|
+
unregisterEditorKernel(editorId);
|
|
809
830
|
try {
|
|
810
|
-
|
|
831
|
+
editor?.setEditorState(createEmptyEditorState());
|
|
811
832
|
} catch (error) {
|
|
812
833
|
this.logger.warn("Failed to reset editor state during destroy:", error);
|
|
813
834
|
}
|
|
814
|
-
this.
|
|
835
|
+
this.unregisterRootLifecycle?.();
|
|
836
|
+
this.unregisterRootLifecycle = void 0;
|
|
837
|
+
let destroyError;
|
|
815
838
|
this.pluginsInstances.forEach((plugin) => {
|
|
816
|
-
|
|
839
|
+
try {
|
|
840
|
+
plugin.destroy?.();
|
|
841
|
+
} catch (error) {
|
|
842
|
+
destroyError ??= error;
|
|
843
|
+
}
|
|
817
844
|
});
|
|
818
845
|
this.pluginsInstances = [];
|
|
819
|
-
this.
|
|
846
|
+
this.dataTypeMap.clear();
|
|
847
|
+
this.decorators = {};
|
|
820
848
|
this.nodeTransforms = [];
|
|
849
|
+
this.nodes = [];
|
|
850
|
+
this.beforeEditorInitHooks = [];
|
|
821
851
|
this.rootClassNames.clear();
|
|
822
852
|
this.serviceMap.clear();
|
|
823
|
-
this.
|
|
824
|
-
this.
|
|
853
|
+
this.themes = { [EDITOR_THEME_KEY]: generateEditorId() };
|
|
854
|
+
this.historyState = createEmptyHistoryState();
|
|
855
|
+
this._commands.clear();
|
|
856
|
+
this._commandsClean.clear();
|
|
857
|
+
this.editor = void 0;
|
|
825
858
|
this.headlessEditor = false;
|
|
826
859
|
this.logger.info("✅ Editor destroyed");
|
|
860
|
+
if (destroyError) throw destroyError;
|
|
827
861
|
}
|
|
828
862
|
getRootElement() {
|
|
829
863
|
if (this.headlessEditor) return null;
|
|
@@ -831,12 +865,23 @@ var Kernel = class Kernel extends EventEmitter {
|
|
|
831
865
|
}
|
|
832
866
|
setRootElement(dom, editable = true) {
|
|
833
867
|
if (this.editor) {
|
|
834
|
-
if (this.headlessEditor)
|
|
868
|
+
if (this.headlessEditor) {
|
|
869
|
+
if (dom === null) return this.editor;
|
|
870
|
+
throw new Error("Headless editor cannot be attached to a root element.");
|
|
871
|
+
}
|
|
835
872
|
this.logger.warn("[Editor] Editor is already initialized, updating root element only");
|
|
873
|
+
if (dom) registerEditorKernel(this.getEditorId(), this);
|
|
836
874
|
this.editor.setRootElement(dom);
|
|
837
|
-
|
|
875
|
+
if (dom) {
|
|
876
|
+
this.editor.setEditable(editable);
|
|
877
|
+
this.applyRootClassNames(dom);
|
|
878
|
+
}
|
|
838
879
|
return this.editor;
|
|
839
880
|
}
|
|
881
|
+
if (dom === null) {
|
|
882
|
+
unregisterEditorKernel(this.getEditorId());
|
|
883
|
+
return null;
|
|
884
|
+
}
|
|
840
885
|
if (this.pluginsInstances.length === 0) {
|
|
841
886
|
this.logger.info(`🔌 Initializing ${this.plugins.length} plugins`);
|
|
842
887
|
for (const plugin of this.plugins) {
|
|
@@ -847,7 +892,7 @@ var Kernel = class Kernel extends EventEmitter {
|
|
|
847
892
|
this.runBeforeEditorInitLifecycle();
|
|
848
893
|
const resolvedNodes = this.resolveNodesForInitialization();
|
|
849
894
|
this.logger.info(`📝 Creating editor with ${resolvedNodes.length} nodes`);
|
|
850
|
-
registerEditorKernel(this.
|
|
895
|
+
registerEditorKernel(this.getEditorId(), this);
|
|
851
896
|
const editor = this.editor = createEditor({
|
|
852
897
|
__kernel: this,
|
|
853
898
|
editable,
|
|
@@ -860,9 +905,9 @@ var Kernel = class Kernel extends EventEmitter {
|
|
|
860
905
|
theme: this.themes
|
|
861
906
|
});
|
|
862
907
|
this.headlessEditor = false;
|
|
908
|
+
this.registerRootLifecycle(editor);
|
|
863
909
|
this.editor.setRootElement(dom);
|
|
864
910
|
this.applyRootClassNames(dom);
|
|
865
|
-
registerEvent(editor, dom);
|
|
866
911
|
this.pluginsInstances.forEach((plugin) => {
|
|
867
912
|
plugin.onInit?.(editor);
|
|
868
913
|
});
|
|
@@ -902,6 +947,7 @@ var Kernel = class Kernel extends EventEmitter {
|
|
|
902
947
|
theme: this.themes
|
|
903
948
|
});
|
|
904
949
|
this.headlessEditor = false;
|
|
950
|
+
this.registerRootLifecycle(editor);
|
|
905
951
|
this.pluginsInstances.forEach((plugin) => {
|
|
906
952
|
plugin.onInit?.(editor);
|
|
907
953
|
});
|
|
@@ -955,6 +1001,9 @@ var Kernel = class Kernel extends EventEmitter {
|
|
|
955
1001
|
this.historyState.current = null;
|
|
956
1002
|
}
|
|
957
1003
|
datasource.read(this.editor, content, options);
|
|
1004
|
+
this.pluginsInstances.forEach((plugin) => {
|
|
1005
|
+
plugin.onDocumentChange?.(type, content, options);
|
|
1006
|
+
});
|
|
958
1007
|
this.emit("documentChange", type, content);
|
|
959
1008
|
this.logger.debug(`📥 Set ${type} document`);
|
|
960
1009
|
}
|
|
@@ -1219,6 +1268,34 @@ var Kernel = class Kernel extends EventEmitter {
|
|
|
1219
1268
|
if (currentRootElement) currentRootElement.classList.remove(...classNames);
|
|
1220
1269
|
};
|
|
1221
1270
|
}
|
|
1271
|
+
registerRootListener(listener) {
|
|
1272
|
+
if (!this.editor || this.headlessEditor) return noop;
|
|
1273
|
+
return this.editor.registerRootListener(listener);
|
|
1274
|
+
}
|
|
1275
|
+
getEditorId() {
|
|
1276
|
+
return this.themes[EDITOR_THEME_KEY];
|
|
1277
|
+
}
|
|
1278
|
+
registerRootLifecycle(editor) {
|
|
1279
|
+
this.unregisterRootLifecycle?.();
|
|
1280
|
+
let rootEventCleanup;
|
|
1281
|
+
const clearRootEvent = () => {
|
|
1282
|
+
rootEventCleanup?.();
|
|
1283
|
+
rootEventCleanup = void 0;
|
|
1284
|
+
};
|
|
1285
|
+
const unregisterRootListener = editor.registerRootListener((rootElement) => {
|
|
1286
|
+
clearRootEvent();
|
|
1287
|
+
if (!rootElement) {
|
|
1288
|
+
unregisterEditorKernel(this.getEditorId());
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1291
|
+
registerEditorKernel(this.getEditorId(), this);
|
|
1292
|
+
rootEventCleanup = registerEvent(editor, rootElement);
|
|
1293
|
+
});
|
|
1294
|
+
this.unregisterRootLifecycle = () => {
|
|
1295
|
+
clearRootEvent();
|
|
1296
|
+
unregisterRootListener();
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1222
1299
|
runBeforeEditorInitLifecycle() {
|
|
1223
1300
|
for (const hook of this.beforeEditorInitHooks) hook();
|
|
1224
1301
|
}
|
|
@@ -1597,8 +1674,309 @@ function registerCursorNode(editor) {
|
|
|
1597
1674
|
}, COMMAND_PRIORITY_HIGH));
|
|
1598
1675
|
}
|
|
1599
1676
|
//#endregion
|
|
1677
|
+
//#region src/plugins/litexml/diff-validation.ts
|
|
1678
|
+
const ACTIONABLE_DIFF_TYPES = /* @__PURE__ */ new Set([
|
|
1679
|
+
"add",
|
|
1680
|
+
"remove",
|
|
1681
|
+
"modify",
|
|
1682
|
+
"listItemAdd",
|
|
1683
|
+
"listItemModify",
|
|
1684
|
+
"listItemRemove"
|
|
1685
|
+
]);
|
|
1686
|
+
const isActionableDiff = (node) => node?.type === "diff" && ACTIONABLE_DIFF_TYPES.has(node.diffType || "");
|
|
1687
|
+
const isElementNode = (node) => Array.isArray(node.children);
|
|
1688
|
+
const cloneNode = (node) => structuredClone(node);
|
|
1689
|
+
function hasActionableDiffDescendant(node) {
|
|
1690
|
+
return (node.children || []).some((child) => isActionableDiff(child) || hasActionableDiffDescendant(child));
|
|
1691
|
+
}
|
|
1692
|
+
/**
|
|
1693
|
+
* Validate the serialized public tree. Paths use serialized node ids, not
|
|
1694
|
+
* Lexical's private node map, so this remains stable across JSON reloads.
|
|
1695
|
+
*/
|
|
1696
|
+
function collectIllegalNestedDiffPaths(root) {
|
|
1697
|
+
const violations = [];
|
|
1698
|
+
const visit = (node, ancestors, path) => {
|
|
1699
|
+
const actionableAncestor = [...ancestors].reverse().find(isActionableDiff);
|
|
1700
|
+
const rowDiffAncestor = [...ancestors].reverse().find((ancestor) => ancestor.type === "table-row-diff");
|
|
1701
|
+
const allowedCellDiffChild = ancestors.at(-1)?.type === "table-cell-diff" && isActionableDiff(node);
|
|
1702
|
+
if (isActionableDiff(node) && actionableAncestor && !allowedCellDiffChild) violations.push(`${path}:diff-inside-diff`);
|
|
1703
|
+
if (rowDiffAncestor) {
|
|
1704
|
+
if (node.type === "table-cell-diff") violations.push(`${path}:cell-inside-row-diff`);
|
|
1705
|
+
else if (isActionableDiff(node)) violations.push(`${path}:diff-inside-row-diff`);
|
|
1706
|
+
}
|
|
1707
|
+
if (actionableAncestor) {
|
|
1708
|
+
if (node.type === "table-cell-diff") violations.push(`${path}:cell-diff-inside-diff`);
|
|
1709
|
+
else if (node.type === "table-row-diff") violations.push(`${path}:row-diff-inside-diff`);
|
|
1710
|
+
}
|
|
1711
|
+
if (!isElementNode(node)) return;
|
|
1712
|
+
node.children?.forEach((child) => {
|
|
1713
|
+
visit(child, [...ancestors, node], `${path}/${child.id || child.type || "node"}`);
|
|
1714
|
+
});
|
|
1715
|
+
};
|
|
1716
|
+
visit(root, [], root.id || root.type || "root");
|
|
1717
|
+
return violations;
|
|
1718
|
+
}
|
|
1719
|
+
function findNewIllegalDiffPaths(previous, projected) {
|
|
1720
|
+
const previousCounts = /* @__PURE__ */ new Map();
|
|
1721
|
+
collectIllegalNestedDiffPaths(previous.root).forEach((path) => {
|
|
1722
|
+
previousCounts.set(path, (previousCounts.get(path) || 0) + 1);
|
|
1723
|
+
});
|
|
1724
|
+
const newPaths = [];
|
|
1725
|
+
const projectedCounts = /* @__PURE__ */ new Map();
|
|
1726
|
+
collectIllegalNestedDiffPaths(projected.root).forEach((path) => {
|
|
1727
|
+
const count = (projectedCounts.get(path) || 0) + 1;
|
|
1728
|
+
projectedCounts.set(path, count);
|
|
1729
|
+
if (count > (previousCounts.get(path) || 0)) newPaths.push(path);
|
|
1730
|
+
});
|
|
1731
|
+
return newPaths;
|
|
1732
|
+
}
|
|
1733
|
+
function findNodeLocation(root, id, ancestors = []) {
|
|
1734
|
+
if (root.id === id) return {
|
|
1735
|
+
ancestors,
|
|
1736
|
+
index: -1,
|
|
1737
|
+
node: root,
|
|
1738
|
+
parent: null
|
|
1739
|
+
};
|
|
1740
|
+
for (const [index, child] of (root.children || []).entries()) {
|
|
1741
|
+
if (child.id === id) return {
|
|
1742
|
+
ancestors: [...ancestors, root],
|
|
1743
|
+
index,
|
|
1744
|
+
node: child,
|
|
1745
|
+
parent: root
|
|
1746
|
+
};
|
|
1747
|
+
const nested = findNodeLocation(child, id, [...ancestors, root]);
|
|
1748
|
+
if (nested) return nested;
|
|
1749
|
+
}
|
|
1750
|
+
return null;
|
|
1751
|
+
}
|
|
1752
|
+
function replaceNode(location, replacements) {
|
|
1753
|
+
if (!location.parent || location.index < 0) return;
|
|
1754
|
+
location.parent.children?.splice(location.index, 1, ...replacements);
|
|
1755
|
+
}
|
|
1756
|
+
function createDiff(diffType, children) {
|
|
1757
|
+
return {
|
|
1758
|
+
children,
|
|
1759
|
+
diffType,
|
|
1760
|
+
type: "diff"
|
|
1761
|
+
};
|
|
1762
|
+
}
|
|
1763
|
+
function createDiffContent(side, children) {
|
|
1764
|
+
return {
|
|
1765
|
+
children,
|
|
1766
|
+
side,
|
|
1767
|
+
type: "diff-content"
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
function projectModifyTarget(root, incoming) {
|
|
1771
|
+
if (!incoming.id) return;
|
|
1772
|
+
const location = findNodeLocation(root, incoming.id);
|
|
1773
|
+
if (!location) return;
|
|
1774
|
+
const target = location.node;
|
|
1775
|
+
const next = cloneNode(incoming);
|
|
1776
|
+
if (target.type === "tablerow" && location.parent?.type === "table") {
|
|
1777
|
+
replaceNode(location, [{
|
|
1778
|
+
children: cloneNode(target.children || []),
|
|
1779
|
+
diffType: "remove",
|
|
1780
|
+
type: "table-row-diff"
|
|
1781
|
+
}, {
|
|
1782
|
+
children: cloneNode(next.children || []),
|
|
1783
|
+
diffType: "add",
|
|
1784
|
+
type: "table-row-diff"
|
|
1785
|
+
}]);
|
|
1786
|
+
return;
|
|
1787
|
+
}
|
|
1788
|
+
if (target.type === "tablecell" || target.type === "table-cell-diff") {
|
|
1789
|
+
const existingDiff = (target.children || []).find((child) => child.type === "diff");
|
|
1790
|
+
if (existingDiff && target.type === "table-cell-diff") {
|
|
1791
|
+
existingDiff.children = cloneNode(next.children || []);
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1794
|
+
const after = existingDiff?.children?.find((child) => child.type === "diff-content" && child.side === "after");
|
|
1795
|
+
if (existingDiff?.diffType === "modify" && after) {
|
|
1796
|
+
after.children = cloneNode(next.children || []);
|
|
1797
|
+
return;
|
|
1798
|
+
}
|
|
1799
|
+
target.children = [createDiff("modify", [createDiffContent("before", cloneNode(target.children || [])), createDiffContent("after", cloneNode(next.children || []))])];
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1802
|
+
if (location.ancestors.some(isActionableDiff)) {
|
|
1803
|
+
replaceNode(location, [next]);
|
|
1804
|
+
return;
|
|
1805
|
+
}
|
|
1806
|
+
replaceNode(location, [createDiff("modify", [cloneNode(target), next])]);
|
|
1807
|
+
}
|
|
1808
|
+
function projectRemoveTarget(root, id) {
|
|
1809
|
+
const location = findNodeLocation(root, id);
|
|
1810
|
+
if (!location) return;
|
|
1811
|
+
if (location.ancestors.some(isActionableDiff)) {
|
|
1812
|
+
replaceNode(location, []);
|
|
1813
|
+
return;
|
|
1814
|
+
}
|
|
1815
|
+
const target = location.node;
|
|
1816
|
+
if (target.type === "tablecell" || target.type === "table-cell-diff") {
|
|
1817
|
+
replaceNode(location, [{
|
|
1818
|
+
children: [createDiff("remove", cloneNode(target.children || []))],
|
|
1819
|
+
diffType: "remove",
|
|
1820
|
+
type: "table-cell-diff"
|
|
1821
|
+
}]);
|
|
1822
|
+
return;
|
|
1823
|
+
}
|
|
1824
|
+
if (target.type === "tablerow" && location.parent?.type === "table") {
|
|
1825
|
+
replaceNode(location, [{
|
|
1826
|
+
children: cloneNode(target.children || []),
|
|
1827
|
+
diffType: "remove",
|
|
1828
|
+
type: "table-row-diff"
|
|
1829
|
+
}]);
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
replaceNode(location, [createDiff("remove", [cloneNode(target)])]);
|
|
1833
|
+
}
|
|
1834
|
+
function projectInsert(root, operation, readXml) {
|
|
1835
|
+
const referenceId = "beforeId" in operation ? operation.beforeId : operation.afterId;
|
|
1836
|
+
const reference = referenceId === "root" ? null : findNodeLocation(root, referenceId)?.node || null;
|
|
1837
|
+
const location = referenceId === "root" ? {
|
|
1838
|
+
ancestors: [root],
|
|
1839
|
+
index: "beforeId" in operation ? 0 : (root.children || []).length - 1,
|
|
1840
|
+
node: root.children?.[0] || root,
|
|
1841
|
+
parent: root
|
|
1842
|
+
} : findNodeLocation(root, referenceId);
|
|
1843
|
+
if (!location) return;
|
|
1844
|
+
const incoming = (readXml(operation.litexml).root?.children || []).map(cloneNode);
|
|
1845
|
+
if (incoming.length === 0 || !location.parent) return;
|
|
1846
|
+
const insertAt = "beforeId" in operation ? location.index : location.index + 1;
|
|
1847
|
+
if (incoming.every((node) => node.type === "tablecell") && reference?.type === "tablecell") {
|
|
1848
|
+
location.parent.children?.splice(insertAt, 0, ...incoming.map((node) => ({
|
|
1849
|
+
children: [createDiff("add", cloneNode(node.children || []))],
|
|
1850
|
+
diffType: "add",
|
|
1851
|
+
type: "table-cell-diff"
|
|
1852
|
+
})));
|
|
1853
|
+
return;
|
|
1854
|
+
}
|
|
1855
|
+
if (incoming.every((node) => node.type === "tablerow") && reference?.type === "tablerow") {
|
|
1856
|
+
location.parent.children?.splice(insertAt, 0, ...incoming.map((node) => ({
|
|
1857
|
+
children: cloneNode(node.children || []),
|
|
1858
|
+
diffType: "add",
|
|
1859
|
+
type: "table-row-diff"
|
|
1860
|
+
})));
|
|
1861
|
+
return;
|
|
1862
|
+
}
|
|
1863
|
+
const actionableAncestor = location.ancestors.findLast(isActionableDiff);
|
|
1864
|
+
const isInlineReference = [
|
|
1865
|
+
"text",
|
|
1866
|
+
"span",
|
|
1867
|
+
"link",
|
|
1868
|
+
"autolink",
|
|
1869
|
+
"linebreak"
|
|
1870
|
+
].includes(reference?.type || "");
|
|
1871
|
+
const blockAncestor = [...location.ancestors].reverse().find((node) => [
|
|
1872
|
+
"paragraph",
|
|
1873
|
+
"heading",
|
|
1874
|
+
"listitem",
|
|
1875
|
+
"table",
|
|
1876
|
+
"tablerow",
|
|
1877
|
+
"tablecell"
|
|
1878
|
+
].includes(node.type || ""));
|
|
1879
|
+
if (!actionableAncestor && blockAncestor && hasActionableDiffDescendant(blockAncestor)) {
|
|
1880
|
+
const wrapped = createDiff("modify", [cloneNode(blockAncestor), cloneNode(blockAncestor)]);
|
|
1881
|
+
const blockLocation = findNodeLocation(root, blockAncestor.id || "");
|
|
1882
|
+
if (blockLocation) replaceNode(blockLocation, [wrapped]);
|
|
1883
|
+
return;
|
|
1884
|
+
}
|
|
1885
|
+
if (actionableAncestor && !isInlineReference) {
|
|
1886
|
+
const originLocation = actionableAncestor.id ? findNodeLocation(root, actionableAncestor.id) : null;
|
|
1887
|
+
if (originLocation?.parent && originLocation.index >= 0) {
|
|
1888
|
+
const originInsertAt = "beforeId" in operation ? originLocation.index : originLocation.index + 1;
|
|
1889
|
+
originLocation.parent.children?.splice(originInsertAt, 0, ...incoming.map((node) => createDiff("add", [node])));
|
|
1890
|
+
return;
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
const inserted = actionableAncestor && isInlineReference ? incoming : incoming.map((node) => createDiff("add", [node]));
|
|
1894
|
+
location.parent.children?.splice(insertAt, 0, ...inserted);
|
|
1895
|
+
}
|
|
1896
|
+
/**
|
|
1897
|
+
* Project one operation on a cloned serialized tree. The real Lexical tree is
|
|
1898
|
+
* untouched; callers compare illegal paths before/after and only dispatch safe
|
|
1899
|
+
* operations to the live editor.
|
|
1900
|
+
*/
|
|
1901
|
+
function projectLiteXmlOperation(document, operation, readXml) {
|
|
1902
|
+
const projected = cloneNode(document);
|
|
1903
|
+
if (operation.action === "modify") (Array.isArray(operation.litexml) ? operation.litexml : [operation.litexml]).forEach((xml) => {
|
|
1904
|
+
(readXml(xml).root?.children || []).forEach((child) => {
|
|
1905
|
+
projectModifyTarget(projected.root, child);
|
|
1906
|
+
});
|
|
1907
|
+
});
|
|
1908
|
+
else if (operation.action === "remove") projectRemoveTarget(projected.root, operation.id);
|
|
1909
|
+
else projectInsert(projected.root, operation, readXml);
|
|
1910
|
+
return projected;
|
|
1911
|
+
}
|
|
1912
|
+
//#endregion
|
|
1913
|
+
//#region src/plugins/litexml/node/DiffContentNode.ts
|
|
1914
|
+
/** Groups one side of a modify diff so it can contain any number of block nodes. */
|
|
1915
|
+
var DiffContentNode = class DiffContentNode extends ElementNode {
|
|
1916
|
+
static getType() {
|
|
1917
|
+
return "diff-content";
|
|
1918
|
+
}
|
|
1919
|
+
static clone(node) {
|
|
1920
|
+
return new DiffContentNode(node.__side, node.__key);
|
|
1921
|
+
}
|
|
1922
|
+
static importJSON(serializedNode) {
|
|
1923
|
+
return $createDiffContentNode(serializedNode.side).updateFromJSON(serializedNode);
|
|
1924
|
+
}
|
|
1925
|
+
static importDOM() {
|
|
1926
|
+
return null;
|
|
1927
|
+
}
|
|
1928
|
+
constructor(side, key) {
|
|
1929
|
+
super(key);
|
|
1930
|
+
this.__side = side;
|
|
1931
|
+
}
|
|
1932
|
+
get side() {
|
|
1933
|
+
return this.__side;
|
|
1934
|
+
}
|
|
1935
|
+
setSide(side) {
|
|
1936
|
+
this.getWritable().__side = side;
|
|
1937
|
+
return this;
|
|
1938
|
+
}
|
|
1939
|
+
updateFromJSON(serializedNode) {
|
|
1940
|
+
return super.updateFromJSON(serializedNode).setSide(serializedNode.side);
|
|
1941
|
+
}
|
|
1942
|
+
exportJSON() {
|
|
1943
|
+
return {
|
|
1944
|
+
...super.exportJSON(),
|
|
1945
|
+
side: this.__side,
|
|
1946
|
+
type: DiffContentNode.getType(),
|
|
1947
|
+
version: 1
|
|
1948
|
+
};
|
|
1949
|
+
}
|
|
1950
|
+
createDOM(_config, _editor) {
|
|
1951
|
+
const element = document.createElement("div");
|
|
1952
|
+
element.dataset.diffContent = this.__side;
|
|
1953
|
+
return element;
|
|
1954
|
+
}
|
|
1955
|
+
updateDOM(previousNode, dom) {
|
|
1956
|
+
if (previousNode.__side !== this.__side) dom.dataset.diffContent = this.__side;
|
|
1957
|
+
return false;
|
|
1958
|
+
}
|
|
1959
|
+
exportDOM(_editor) {
|
|
1960
|
+
const element = document.createElement("div");
|
|
1961
|
+
element.dataset.diffContent = this.__side;
|
|
1962
|
+
return { element };
|
|
1963
|
+
}
|
|
1964
|
+
canBeEmpty() {
|
|
1965
|
+
return true;
|
|
1966
|
+
}
|
|
1967
|
+
isInline() {
|
|
1968
|
+
return false;
|
|
1969
|
+
}
|
|
1970
|
+
};
|
|
1971
|
+
function $createDiffContentNode(side) {
|
|
1972
|
+
return $applyNodeReplacement(new DiffContentNode(side));
|
|
1973
|
+
}
|
|
1974
|
+
function $isDiffContentNode(node) {
|
|
1975
|
+
return node instanceof DiffContentNode;
|
|
1976
|
+
}
|
|
1977
|
+
//#endregion
|
|
1600
1978
|
//#region src/plugins/litexml/node/DiffNode.ts
|
|
1601
|
-
/** DiffNode - contains
|
|
1979
|
+
/** DiffNode - contains the original and modified sides, optionally grouped into multiple blocks. */
|
|
1602
1980
|
var DiffNode = class DiffNode extends CardLikeElementNode {
|
|
1603
1981
|
static getType() {
|
|
1604
1982
|
return "diff";
|
|
@@ -1690,6 +2068,83 @@ var DiffNode = class DiffNode extends CardLikeElementNode {
|
|
|
1690
2068
|
function $createDiffNode(diffType = "unchanged") {
|
|
1691
2069
|
return $applyNodeReplacement(new DiffNode(diffType));
|
|
1692
2070
|
}
|
|
2071
|
+
function $isDiffNode(node) {
|
|
2072
|
+
return node instanceof DiffNode;
|
|
2073
|
+
}
|
|
2074
|
+
//#endregion
|
|
2075
|
+
//#region src/plugins/litexml/node/TableCellDiffNode.ts
|
|
2076
|
+
/**
|
|
2077
|
+
* Review-only table cell. The diff state lives on a TableCellNode subclass so
|
|
2078
|
+
* TableRowNode children remain cells throughout staging and approval.
|
|
2079
|
+
*/
|
|
2080
|
+
var TableCellDiffNode = class TableCellDiffNode extends TableCellNode {
|
|
2081
|
+
static getType() {
|
|
2082
|
+
return "table-cell-diff";
|
|
2083
|
+
}
|
|
2084
|
+
static clone(node) {
|
|
2085
|
+
const clone = new TableCellDiffNode(node.__diffType, node.__changeId, node.getHeaderStyles(), node.getColSpan(), node.getWidth(), node.__key);
|
|
2086
|
+
clone.__rowSpan = node.__rowSpan;
|
|
2087
|
+
clone.__backgroundColor = node.__backgroundColor;
|
|
2088
|
+
clone.__verticalAlign = node.__verticalAlign;
|
|
2089
|
+
return clone;
|
|
2090
|
+
}
|
|
2091
|
+
static importJSON(serializedNode) {
|
|
2092
|
+
return $createTableCellDiffNode(serializedNode.diffType, serializedNode.changeId, serializedNode.headerState, serializedNode.colSpan, serializedNode.width).updateFromJSON(serializedNode);
|
|
2093
|
+
}
|
|
2094
|
+
constructor(diffType, changeId, headerState, colSpan, width, key) {
|
|
2095
|
+
super(headerState, colSpan, width, key);
|
|
2096
|
+
this.__diffType = diffType;
|
|
2097
|
+
this.__changeId = changeId;
|
|
2098
|
+
}
|
|
2099
|
+
createDOM(config) {
|
|
2100
|
+
const element = super.createDOM(config);
|
|
2101
|
+
const theme = config.theme.tableCellDiff;
|
|
2102
|
+
if (typeof theme === "string" && theme) element.classList.add(theme);
|
|
2103
|
+
this.syncDOMState(element);
|
|
2104
|
+
return element;
|
|
2105
|
+
}
|
|
2106
|
+
updateDOM(prevNode) {
|
|
2107
|
+
return super.updateDOM(prevNode) || prevNode.__diffType !== this.__diffType || prevNode.__changeId !== this.__changeId;
|
|
2108
|
+
}
|
|
2109
|
+
exportJSON() {
|
|
2110
|
+
return {
|
|
2111
|
+
...super.exportJSON(),
|
|
2112
|
+
changeId: this.__changeId,
|
|
2113
|
+
diffType: this.__diffType,
|
|
2114
|
+
type: TableCellDiffNode.getType(),
|
|
2115
|
+
version: 1
|
|
2116
|
+
};
|
|
2117
|
+
}
|
|
2118
|
+
getChangeId() {
|
|
2119
|
+
return this.getLatest().__changeId;
|
|
2120
|
+
}
|
|
2121
|
+
getDiffType() {
|
|
2122
|
+
return this.getLatest().__diffType;
|
|
2123
|
+
}
|
|
2124
|
+
setChangeId(changeId) {
|
|
2125
|
+
this.getWritable().__changeId = changeId;
|
|
2126
|
+
return this;
|
|
2127
|
+
}
|
|
2128
|
+
setDiffType(diffType) {
|
|
2129
|
+
this.getWritable().__diffType = diffType;
|
|
2130
|
+
return this;
|
|
2131
|
+
}
|
|
2132
|
+
updateFromJSON(serializedNode) {
|
|
2133
|
+
return super.updateFromJSON(serializedNode).setDiffType(serializedNode.diffType).setChangeId(serializedNode.changeId);
|
|
2134
|
+
}
|
|
2135
|
+
syncDOMState(element) {
|
|
2136
|
+
element.contentEditable = "false";
|
|
2137
|
+
element.dataset.diffType = this.__diffType;
|
|
2138
|
+
if (this.__changeId) element.dataset.diffChangeId = this.__changeId;
|
|
2139
|
+
else delete element.dataset.diffChangeId;
|
|
2140
|
+
}
|
|
2141
|
+
};
|
|
2142
|
+
function $createTableCellDiffNode(diffType, changeId, headerState, colSpan, width) {
|
|
2143
|
+
return $applyNodeReplacement(new TableCellDiffNode(diffType, changeId, headerState, colSpan, width));
|
|
2144
|
+
}
|
|
2145
|
+
function $isTableCellDiffNode(node) {
|
|
2146
|
+
return node instanceof TableCellDiffNode;
|
|
2147
|
+
}
|
|
1693
2148
|
//#endregion
|
|
1694
2149
|
//#region src/plugins/litexml/utils/index.ts
|
|
1695
2150
|
const getNumericId$1 = (id) => {
|
|
@@ -1748,11 +2203,333 @@ function charToId(char) {
|
|
|
1748
2203
|
return String((parseInt(char, 36) - startId + maxId) * modInverse % maxId);
|
|
1749
2204
|
}
|
|
1750
2205
|
//#endregion
|
|
2206
|
+
//#region src/plugins/litexml/table-cell-diff.ts
|
|
2207
|
+
function copyCellStructure(source, target) {
|
|
2208
|
+
target.setRowSpan(source.getRowSpan());
|
|
2209
|
+
target.setBackgroundColor(source.getBackgroundColor());
|
|
2210
|
+
target.setVerticalAlign(source.getVerticalAlign());
|
|
2211
|
+
}
|
|
2212
|
+
function $createTableCellDiffFromCell(editor, cell, diffType, changeId) {
|
|
2213
|
+
const diffCell = $createTableCellDiffNode(diffType, changeId, cell.getHeaderStyles(), cell.getColSpan(), cell.getWidth());
|
|
2214
|
+
copyCellStructure(cell, diffCell);
|
|
2215
|
+
const diff = $createDiffNode(diffType);
|
|
2216
|
+
diff.append(...cell.getChildren().map((child) => $cloneNode(child, editor)));
|
|
2217
|
+
diffCell.append(diff);
|
|
2218
|
+
return diffCell;
|
|
2219
|
+
}
|
|
2220
|
+
function $createPlainTableCellFromDiff(editor, cell) {
|
|
2221
|
+
const plainCell = $createTableCellNode(cell.getHeaderStyles(), cell.getColSpan(), cell.getWidth());
|
|
2222
|
+
copyCellStructure(cell, plainCell);
|
|
2223
|
+
const diff = cell.getFirstChild();
|
|
2224
|
+
if (diff instanceof DiffNode) plainCell.append(...diff.getChildren().map((child) => $cloneNode(child, editor)));
|
|
2225
|
+
else plainCell.append(...cell.getChildren().map((child) => $cloneNode(child, editor)));
|
|
2226
|
+
return plainCell;
|
|
2227
|
+
}
|
|
2228
|
+
function $getTableForCell(cell) {
|
|
2229
|
+
const row = cell.getParent();
|
|
2230
|
+
if (!$isTableRowNode(row)) return null;
|
|
2231
|
+
const table = row.getParent();
|
|
2232
|
+
return $isTableNode(table) ? table : null;
|
|
2233
|
+
}
|
|
2234
|
+
function $getTableCellColumnIndex(cell) {
|
|
2235
|
+
const row = cell.getParent();
|
|
2236
|
+
if (!$isTableRowNode(row)) return -1;
|
|
2237
|
+
let index = 0;
|
|
2238
|
+
for (const sibling of row.getChildren()) {
|
|
2239
|
+
if (sibling === cell) return index;
|
|
2240
|
+
if ($isTableCellNode(sibling)) index += sibling.getColSpan();
|
|
2241
|
+
}
|
|
2242
|
+
return -1;
|
|
2243
|
+
}
|
|
2244
|
+
function $getLogicalRowWidth(cell) {
|
|
2245
|
+
const row = cell.getParent();
|
|
2246
|
+
if (!$isTableRowNode(row)) return 0;
|
|
2247
|
+
return row.getChildren().reduce((total, child) => total + ($isTableCellNode(child) ? child.getColSpan() : 0), 0);
|
|
2248
|
+
}
|
|
2249
|
+
function $getTableCellDiffGroup(cell) {
|
|
2250
|
+
const changeId = cell.getChangeId();
|
|
2251
|
+
const table = $getTableForCell(cell);
|
|
2252
|
+
if (!changeId || !table) return [cell];
|
|
2253
|
+
return $nodesOfType(TableCellDiffNode).filter((candidate) => candidate.getChangeId() === changeId && candidate.getDiffType() === cell.getDiffType() && $getTableForCell(candidate) === table);
|
|
2254
|
+
}
|
|
2255
|
+
function $updateTableWidthsForCellInsertion(table, rowWidthBefore, columnIndex, insertedSpan) {
|
|
2256
|
+
const currentWidths = table.getColWidths() || Array.from({ length: rowWidthBefore }, () => 250);
|
|
2257
|
+
if (rowWidthBefore < currentWidths.length) return;
|
|
2258
|
+
const fallback = currentWidths[Math.max(0, columnIndex - 1)] || 250;
|
|
2259
|
+
const next = [...currentWidths];
|
|
2260
|
+
next.splice(columnIndex, 0, ...Array.from({ length: insertedSpan }, () => fallback));
|
|
2261
|
+
table.setColWidths(next);
|
|
2262
|
+
}
|
|
2263
|
+
function $removeTableWidthsForCompleteCellGroup(table, group, columnIndex, span) {
|
|
2264
|
+
const rows = table.getChildren().filter($isTableRowNode);
|
|
2265
|
+
const rowKeys = new Set(group.map((cell) => cell.getParent()?.getKey()));
|
|
2266
|
+
if (rows.length !== rowKeys.size) return;
|
|
2267
|
+
const widths = table.getColWidths();
|
|
2268
|
+
if (!widths) return;
|
|
2269
|
+
const next = [...widths];
|
|
2270
|
+
next.splice(columnIndex, span);
|
|
2271
|
+
table.setColWidths(next);
|
|
2272
|
+
}
|
|
2273
|
+
function $shrinkTableWidthsAfterCellRemoval(table, columnIndex, span) {
|
|
2274
|
+
const widths = table.getColWidths();
|
|
2275
|
+
if (!widths) return;
|
|
2276
|
+
if (table.getChildren().reduce((max, row) => {
|
|
2277
|
+
if (!$isTableRowNode(row)) return max;
|
|
2278
|
+
return Math.max(max, row.getChildren().reduce((total, cell) => total + ($isTableCellNode(cell) ? cell.getColSpan() : 0), 0));
|
|
2279
|
+
}, 0) > widths.length - span) return;
|
|
2280
|
+
const next = [...widths];
|
|
2281
|
+
next.splice(columnIndex, span);
|
|
2282
|
+
table.setColWidths(next);
|
|
2283
|
+
}
|
|
2284
|
+
function $getMaximumLogicalColumnCount(table) {
|
|
2285
|
+
return table.getChildren().reduce((maximum, row) => {
|
|
2286
|
+
if (!$isTableRowNode(row)) return maximum;
|
|
2287
|
+
const columnCount = row.getChildren().reduce((total, cell) => {
|
|
2288
|
+
return total + ($isTableCellNode(cell) ? cell.getColSpan() : 0);
|
|
2289
|
+
}, 0);
|
|
2290
|
+
return Math.max(maximum, columnCount);
|
|
2291
|
+
}, 0);
|
|
2292
|
+
}
|
|
2293
|
+
function $hasLegacyTableCellDiffs() {
|
|
2294
|
+
return $nodesOfType(DiffNode).some((diff) => {
|
|
2295
|
+
return (diff.diffType === "add" || diff.diffType === "remove") && $isTableRowNode(diff.getParent()) && diff.getChildren().some($isTableCellNode);
|
|
2296
|
+
});
|
|
2297
|
+
}
|
|
2298
|
+
function $hasIncompleteTableWidths() {
|
|
2299
|
+
return $nodesOfType(TableNode).some((table) => {
|
|
2300
|
+
return (table.getColWidths()?.length || 0) < $getMaximumLogicalColumnCount(table);
|
|
2301
|
+
});
|
|
2302
|
+
}
|
|
2303
|
+
/**
|
|
2304
|
+
* Migrates the old `TableRow > DiffNode > TableCellNode` representation into
|
|
2305
|
+
* cell-shaped diff nodes and makes sure the table has a width for every
|
|
2306
|
+
* logical column. Safe to call repeatedly.
|
|
2307
|
+
*/
|
|
2308
|
+
function $normalizeLegacyTableCellDiffs(editor) {
|
|
2309
|
+
let changed = false;
|
|
2310
|
+
$nodesOfType(DiffNode).forEach((legacyDiff) => {
|
|
2311
|
+
const diffType = legacyDiff.diffType;
|
|
2312
|
+
if (diffType !== "add" && diffType !== "remove" || !$isTableRowNode(legacyDiff.getParent())) return;
|
|
2313
|
+
const cells = legacyDiff.getChildren().filter($isTableCellNode);
|
|
2314
|
+
if (cells.length === 0) return;
|
|
2315
|
+
const changeId = `legacy-table-cell-${legacyDiff.getKey()}`;
|
|
2316
|
+
cells.forEach((cell) => {
|
|
2317
|
+
legacyDiff.insertBefore($createTableCellDiffFromCell(editor, cell, diffType, changeId));
|
|
2318
|
+
});
|
|
2319
|
+
legacyDiff.remove();
|
|
2320
|
+
changed = true;
|
|
2321
|
+
});
|
|
2322
|
+
$nodesOfType(TableNode).forEach((table) => {
|
|
2323
|
+
const requiredWidthCount = $getMaximumLogicalColumnCount(table);
|
|
2324
|
+
const currentWidths = table.getColWidths() || [];
|
|
2325
|
+
if (currentWidths.length >= requiredWidthCount) return;
|
|
2326
|
+
const fallback = currentWidths.at(-1) || 250;
|
|
2327
|
+
table.setColWidths([...currentWidths, ...Array.from({ length: requiredWidthCount - currentWidths.length }, () => fallback)]);
|
|
2328
|
+
changed = true;
|
|
2329
|
+
});
|
|
2330
|
+
return changed;
|
|
2331
|
+
}
|
|
2332
|
+
function registerLegacyTableCellDiffNormalization(editor) {
|
|
2333
|
+
let destroyed = false;
|
|
2334
|
+
let scheduled = false;
|
|
2335
|
+
const unregister = editor.registerUpdateListener(() => {
|
|
2336
|
+
if (scheduled || destroyed) return;
|
|
2337
|
+
scheduled = true;
|
|
2338
|
+
queueMicrotask(() => {
|
|
2339
|
+
scheduled = false;
|
|
2340
|
+
if (destroyed) return;
|
|
2341
|
+
if (!editor.getEditorState().read(() => $hasLegacyTableCellDiffs() || $hasIncompleteTableWidths())) return;
|
|
2342
|
+
editor.update(() => {
|
|
2343
|
+
$normalizeLegacyTableCellDiffs(editor);
|
|
2344
|
+
}, { discrete: true });
|
|
2345
|
+
});
|
|
2346
|
+
});
|
|
2347
|
+
return () => {
|
|
2348
|
+
destroyed = true;
|
|
2349
|
+
unregister();
|
|
2350
|
+
};
|
|
2351
|
+
}
|
|
2352
|
+
//#endregion
|
|
2353
|
+
//#region src/plugins/litexml/node/TableRowDiffNode.ts
|
|
2354
|
+
/**
|
|
2355
|
+
* A review-only table row. Keeping the diff state on a TableRowNode subclass
|
|
2356
|
+
* preserves Lexical's table invariant: TableNode children are always rows.
|
|
2357
|
+
*/
|
|
2358
|
+
var TableRowDiffNode = class TableRowDiffNode extends TableRowNode {
|
|
2359
|
+
static getType() {
|
|
2360
|
+
return "table-row-diff";
|
|
2361
|
+
}
|
|
2362
|
+
static clone(node) {
|
|
2363
|
+
return new TableRowDiffNode(node.__diffType, node.__changeId, node.getHeight(), node.__key);
|
|
2364
|
+
}
|
|
2365
|
+
static importJSON(serializedNode) {
|
|
2366
|
+
return $createTableRowDiffNode(serializedNode.diffType, serializedNode.changeId, serializedNode.height).updateFromJSON(serializedNode);
|
|
2367
|
+
}
|
|
2368
|
+
constructor(diffType, changeId, height, key) {
|
|
2369
|
+
super(height, key);
|
|
2370
|
+
this.__diffType = diffType;
|
|
2371
|
+
this.__changeId = changeId;
|
|
2372
|
+
}
|
|
2373
|
+
createDOM(config) {
|
|
2374
|
+
const element = super.createDOM(config);
|
|
2375
|
+
const theme = config.theme.tableRowDiff;
|
|
2376
|
+
if (typeof theme === "string" && theme) element.classList.add(theme);
|
|
2377
|
+
this.syncDOMState(element);
|
|
2378
|
+
return element;
|
|
2379
|
+
}
|
|
2380
|
+
exportJSON() {
|
|
2381
|
+
return {
|
|
2382
|
+
...super.exportJSON(),
|
|
2383
|
+
changeId: this.__changeId,
|
|
2384
|
+
diffType: this.__diffType,
|
|
2385
|
+
type: TableRowDiffNode.getType(),
|
|
2386
|
+
version: 1
|
|
2387
|
+
};
|
|
2388
|
+
}
|
|
2389
|
+
getChangeId() {
|
|
2390
|
+
return this.getLatest().__changeId;
|
|
2391
|
+
}
|
|
2392
|
+
getDiffType() {
|
|
2393
|
+
return this.getLatest().__diffType;
|
|
2394
|
+
}
|
|
2395
|
+
setChangeId(changeId) {
|
|
2396
|
+
this.getWritable().__changeId = changeId;
|
|
2397
|
+
return this;
|
|
2398
|
+
}
|
|
2399
|
+
setDiffType(diffType) {
|
|
2400
|
+
this.getWritable().__diffType = diffType;
|
|
2401
|
+
return this;
|
|
2402
|
+
}
|
|
2403
|
+
updateDOM(prevNode) {
|
|
2404
|
+
return super.updateDOM(prevNode) || prevNode.__diffType !== this.__diffType || prevNode.__changeId !== this.__changeId;
|
|
2405
|
+
}
|
|
2406
|
+
updateFromJSON(serializedNode) {
|
|
2407
|
+
return super.updateFromJSON(serializedNode).setDiffType(serializedNode.diffType).setChangeId(serializedNode.changeId);
|
|
2408
|
+
}
|
|
2409
|
+
syncDOMState(element) {
|
|
2410
|
+
element.dataset.diffType = this.__diffType;
|
|
2411
|
+
if (this.__changeId) element.dataset.diffChangeId = this.__changeId;
|
|
2412
|
+
else delete element.dataset.diffChangeId;
|
|
2413
|
+
element.contentEditable = "false";
|
|
2414
|
+
}
|
|
2415
|
+
};
|
|
2416
|
+
function $createTableRowDiffNode(diffType, changeId, height) {
|
|
2417
|
+
return $applyNodeReplacement(new TableRowDiffNode(diffType, changeId, height));
|
|
2418
|
+
}
|
|
2419
|
+
function $isTableRowDiffNode(node) {
|
|
2420
|
+
return node instanceof TableRowDiffNode;
|
|
2421
|
+
}
|
|
2422
|
+
//#endregion
|
|
2423
|
+
//#region src/plugins/litexml/table-row-diff.ts
|
|
2424
|
+
function $areTableRowStructuresCompatible(previousRow, nextRow) {
|
|
2425
|
+
const previousCells = previousRow.getChildren();
|
|
2426
|
+
const nextCells = nextRow.getChildren();
|
|
2427
|
+
if (previousCells.length !== nextCells.length || !previousCells.every($isTableCellNode) || !nextCells.every($isTableCellNode)) return false;
|
|
2428
|
+
return previousCells.every((previousCell, index) => {
|
|
2429
|
+
const nextCell = nextCells[index];
|
|
2430
|
+
return $isTableCellNode(nextCell) && previousCell.getColSpan() === nextCell.getColSpan() && previousCell.getRowSpan() === nextCell.getRowSpan();
|
|
2431
|
+
});
|
|
2432
|
+
}
|
|
2433
|
+
function $createTableRowDiffFromRow(editor, row, diffType, changeId) {
|
|
2434
|
+
const source = $cloneNode(row, editor);
|
|
2435
|
+
const diffRow = $createTableRowDiffNode(diffType, changeId, row.getHeight());
|
|
2436
|
+
diffRow.append(...source.getChildren());
|
|
2437
|
+
return diffRow;
|
|
2438
|
+
}
|
|
2439
|
+
function $createPlainTableRowFromDiff(editor, row) {
|
|
2440
|
+
const plainRow = $createTableRowNode(row.getHeight());
|
|
2441
|
+
plainRow.append(...row.getChildren().map((child) => $cloneNode(child, editor)));
|
|
2442
|
+
return plainRow;
|
|
2443
|
+
}
|
|
2444
|
+
function $getTableRowDiffPair(node) {
|
|
2445
|
+
const changeId = node.getChangeId();
|
|
2446
|
+
if (!changeId) return null;
|
|
2447
|
+
const matchingRows = $nodesOfType(TableRowDiffNode).filter((candidate) => candidate.getChangeId() === changeId);
|
|
2448
|
+
if (matchingRows.length !== 2) return null;
|
|
2449
|
+
const remove = matchingRows.find((candidate) => candidate.getDiffType() === "remove");
|
|
2450
|
+
const add = matchingRows.find((candidate) => candidate.getDiffType() === "add");
|
|
2451
|
+
if (!remove || !add) return null;
|
|
2452
|
+
const parent = remove.getParent();
|
|
2453
|
+
if (!$isTableNode(parent) || add.getParent() !== parent || remove.getNextSibling() !== add || !$areTableRowStructuresCompatible(remove, add)) return null;
|
|
2454
|
+
return {
|
|
2455
|
+
add,
|
|
2456
|
+
remove
|
|
2457
|
+
};
|
|
2458
|
+
}
|
|
2459
|
+
function $normalizeTableRowDiffPairs() {
|
|
2460
|
+
const rows = $nodesOfType(TableRowDiffNode);
|
|
2461
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2462
|
+
rows.forEach((row) => {
|
|
2463
|
+
const changeId = row.getChangeId();
|
|
2464
|
+
if (!changeId) return;
|
|
2465
|
+
const group = groups.get(changeId) || [];
|
|
2466
|
+
group.push(row);
|
|
2467
|
+
groups.set(changeId, group);
|
|
2468
|
+
});
|
|
2469
|
+
let changed = false;
|
|
2470
|
+
groups.forEach((group) => {
|
|
2471
|
+
if (group.length === 2 ? $getTableRowDiffPair(group[0]) : null) return;
|
|
2472
|
+
group.forEach((row) => row.setChangeId());
|
|
2473
|
+
changed = true;
|
|
2474
|
+
});
|
|
2475
|
+
return changed;
|
|
2476
|
+
}
|
|
2477
|
+
function registerTableRowDiffNormalization(editor) {
|
|
2478
|
+
let destroyed = false;
|
|
2479
|
+
let scheduled = false;
|
|
2480
|
+
const unregister = editor.registerUpdateListener(() => {
|
|
2481
|
+
if (scheduled || destroyed) return;
|
|
2482
|
+
scheduled = true;
|
|
2483
|
+
queueMicrotask(() => {
|
|
2484
|
+
scheduled = false;
|
|
2485
|
+
if (destroyed) return;
|
|
2486
|
+
if (!editor.getEditorState().read(() => {
|
|
2487
|
+
return $nodesOfType(TableRowDiffNode).some((row) => row.getChangeId() && !$getTableRowDiffPair(row));
|
|
2488
|
+
})) return;
|
|
2489
|
+
editor.update(() => {
|
|
2490
|
+
$normalizeTableRowDiffPairs();
|
|
2491
|
+
});
|
|
2492
|
+
});
|
|
2493
|
+
});
|
|
2494
|
+
return () => {
|
|
2495
|
+
destroyed = true;
|
|
2496
|
+
unregister();
|
|
2497
|
+
};
|
|
2498
|
+
}
|
|
2499
|
+
//#endregion
|
|
1751
2500
|
//#region src/plugins/litexml/command/index.ts
|
|
1752
2501
|
const logger$7 = createDebugLogger("plugin", "litexml");
|
|
1753
2502
|
function toArrayXml(litexml) {
|
|
1754
2503
|
return Array.isArray(litexml) ? litexml : [litexml];
|
|
1755
2504
|
}
|
|
2505
|
+
function hasNewIllegalDiffs(previous, projected) {
|
|
2506
|
+
return findNewIllegalDiffPaths(previous, projected);
|
|
2507
|
+
}
|
|
2508
|
+
function projectOperation(dataSource, document, operation) {
|
|
2509
|
+
try {
|
|
2510
|
+
const projected = projectLiteXmlOperation(document, operation, (xml) => dataSource.readLiteXMLToInode(xml));
|
|
2511
|
+
const newIllegalDiffs = hasNewIllegalDiffs(document, projected);
|
|
2512
|
+
if (newIllegalDiffs.length > 0) {
|
|
2513
|
+
logger$7.warn("⚠️ Skipping operation with illegal nested diff", newIllegalDiffs);
|
|
2514
|
+
return null;
|
|
2515
|
+
}
|
|
2516
|
+
return projected;
|
|
2517
|
+
} catch (error) {
|
|
2518
|
+
logger$7.error("❌ Failed to preflight LiteXML operation:", error);
|
|
2519
|
+
return null;
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
function toProjectionOperation(operation) {
|
|
2523
|
+
if (operation.action === "remove") return {
|
|
2524
|
+
...operation,
|
|
2525
|
+
id: charToId(operation.id)
|
|
2526
|
+
};
|
|
2527
|
+
if (operation.action === "insert") return {
|
|
2528
|
+
...operation,
|
|
2529
|
+
..."beforeId" in operation ? { beforeId: operation.beforeId === "root" ? "root" : charToId(operation.beforeId) } : { afterId: operation.afterId === "root" ? "root" : charToId(operation.afterId) }
|
|
2530
|
+
};
|
|
2531
|
+
return operation;
|
|
2532
|
+
}
|
|
1756
2533
|
function tryParseChild(child, editor) {
|
|
1757
2534
|
try {
|
|
1758
2535
|
const oldNode = $getNodeByKey(child.id);
|
|
@@ -1769,15 +2546,56 @@ function tryParseChild(child, editor) {
|
|
|
1769
2546
|
}
|
|
1770
2547
|
}
|
|
1771
2548
|
function handleReplaceForApplyDelay(oldNode, newNode, modifyBlockNodes, diffNodeMap, editor) {
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
2549
|
+
if ($isTableRowNode(oldNode) || $isTableRowNode(newNode)) {
|
|
2550
|
+
if (!$isTableRowNode(oldNode) || !$isTableRowNode(newNode) || !$isTableNode(oldNode.getParent()) || !$areTableRowStructuresCompatible(oldNode, newNode)) {
|
|
2551
|
+
logger$7.error(`❌ Invalid table row modification for row ${oldNode.getKey()}.`);
|
|
2552
|
+
return;
|
|
2553
|
+
}
|
|
2554
|
+
const changeId = `${oldNode.getKey()}:${newNode.getKey()}`;
|
|
2555
|
+
const removeRow = $createTableRowDiffFromRow(editor, oldNode, "remove", changeId);
|
|
2556
|
+
const addRow = $createTableRowDiffFromRow(editor, newNode, "add", changeId);
|
|
2557
|
+
oldNode.replace(removeRow, false);
|
|
2558
|
+
removeRow.insertAfter(addRow);
|
|
1776
2559
|
return;
|
|
1777
2560
|
}
|
|
1778
|
-
if (oldNode
|
|
1779
|
-
const
|
|
1780
|
-
|
|
2561
|
+
if ($isTableCellNode(oldNode) && $isTableCellNode(newNode)) {
|
|
2562
|
+
const existingDiff = oldNode.getChildren().find($isDiffNode);
|
|
2563
|
+
if (existingDiff) {
|
|
2564
|
+
if ($isTableCellDiffNode(oldNode)) {
|
|
2565
|
+
existingDiff.clear();
|
|
2566
|
+
newNode.getChildren().forEach((child) => {
|
|
2567
|
+
existingDiff.append($cloneNode(child, editor));
|
|
2568
|
+
});
|
|
2569
|
+
return;
|
|
2570
|
+
}
|
|
2571
|
+
const after = existingDiff.getChildren().find((child) => $isDiffContentNode(child) && child.side === "after");
|
|
2572
|
+
if (existingDiff.diffType === "modify" && after && $isDiffContentNode(after)) {
|
|
2573
|
+
after.clear();
|
|
2574
|
+
newNode.getChildren().forEach((child) => {
|
|
2575
|
+
after.append($cloneNode(child, editor));
|
|
2576
|
+
});
|
|
2577
|
+
return;
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
const before = $createDiffContentNode("before");
|
|
2581
|
+
const after = $createDiffContentNode("after");
|
|
2582
|
+
oldNode.getChildren().forEach((child) => before.append($cloneNode(child, editor)));
|
|
2583
|
+
newNode.getChildren().forEach((child) => after.append($cloneNode(child, editor)));
|
|
2584
|
+
const diffNode = $createDiffNode("modify");
|
|
2585
|
+
diffNode.append(before, after);
|
|
2586
|
+
oldNode.clear();
|
|
2587
|
+
oldNode.append(diffNode);
|
|
2588
|
+
return;
|
|
2589
|
+
}
|
|
2590
|
+
const oldBlock = $closest(oldNode, (node) => node.isInline() === false);
|
|
2591
|
+
if (!oldBlock) throw new Error("Old block node not found for diffing.");
|
|
2592
|
+
if ($closest(oldNode, (node) => node.getType() === DiffNode.getType())) {
|
|
2593
|
+
oldNode.replace(newNode, false);
|
|
2594
|
+
return;
|
|
2595
|
+
}
|
|
2596
|
+
if (oldNode === oldBlock) {
|
|
2597
|
+
const diffNode = $createDiffNode("modify");
|
|
2598
|
+
diffNode.append($cloneNode(oldBlock, editor), newNode);
|
|
1781
2599
|
oldNode.replace(diffNode, false);
|
|
1782
2600
|
} else {
|
|
1783
2601
|
if (!modifyBlockNodes.has(oldBlock.getKey())) {
|
|
@@ -1852,8 +2670,15 @@ function registerLiteXMLCommand(editor, dataSource) {
|
|
|
1852
2670
|
else acc.push(cur);
|
|
1853
2671
|
return acc;
|
|
1854
2672
|
}, []);
|
|
2673
|
+
let projectedDocument = getKernelFromEditor(editor).getDocument("json");
|
|
2674
|
+
const safePayload = resultPayload.filter((item) => {
|
|
2675
|
+
const nextProjection = projectOperation(dataSource, projectedDocument, toProjectionOperation(item));
|
|
2676
|
+
if (!nextProjection) return false;
|
|
2677
|
+
projectedDocument = nextProjection;
|
|
2678
|
+
return true;
|
|
2679
|
+
});
|
|
1855
2680
|
try {
|
|
1856
|
-
|
|
2681
|
+
safePayload.forEach((item) => {
|
|
1857
2682
|
const { action } = item;
|
|
1858
2683
|
switch (action) {
|
|
1859
2684
|
case "modify": {
|
|
@@ -1882,14 +2707,39 @@ function registerLiteXMLCommand(editor, dataSource) {
|
|
|
1882
2707
|
}
|
|
1883
2708
|
}, COMMAND_PRIORITY_EDITOR), editor.registerCommand(LITEXML_APPLY_COMMAND, (payload) => {
|
|
1884
2709
|
const { litexml, delay } = payload;
|
|
1885
|
-
|
|
2710
|
+
const arrayXml = toArrayXml(litexml);
|
|
2711
|
+
if (!delay) {
|
|
2712
|
+
handleModify(editor, dataSource, arrayXml, delay);
|
|
2713
|
+
return false;
|
|
2714
|
+
}
|
|
2715
|
+
const operation = {
|
|
2716
|
+
action: "modify",
|
|
2717
|
+
litexml
|
|
2718
|
+
};
|
|
2719
|
+
if (projectOperation(dataSource, getKernelFromEditor(editor).getDocument("json"), toProjectionOperation(operation))) handleModify(editor, dataSource, arrayXml, delay);
|
|
1886
2720
|
return false;
|
|
1887
2721
|
}, COMMAND_PRIORITY_EDITOR), editor.registerCommand(LITEXML_REMOVE_COMMAND, (payload) => {
|
|
1888
2722
|
const { id, delay } = payload;
|
|
1889
|
-
|
|
2723
|
+
const key = charToId(id);
|
|
2724
|
+
if (!delay) {
|
|
2725
|
+
handleRemove(editor, key, delay);
|
|
2726
|
+
return false;
|
|
2727
|
+
}
|
|
2728
|
+
const operation = {
|
|
2729
|
+
action: "remove",
|
|
2730
|
+
id
|
|
2731
|
+
};
|
|
2732
|
+
if (projectOperation(dataSource, getKernelFromEditor(editor).getDocument("json"), toProjectionOperation(operation))) handleRemove(editor, key, delay);
|
|
1890
2733
|
return false;
|
|
1891
2734
|
}, COMMAND_PRIORITY_EDITOR), editor.registerCommand(LITEXML_INSERT_COMMAND, (payload) => {
|
|
1892
|
-
|
|
2735
|
+
if (!payload.delay) {
|
|
2736
|
+
handleInsert(editor, payload, dataSource);
|
|
2737
|
+
return false;
|
|
2738
|
+
}
|
|
2739
|
+
if (projectOperation(dataSource, getKernelFromEditor(editor).getDocument("json"), toProjectionOperation({
|
|
2740
|
+
action: "insert",
|
|
2741
|
+
...payload
|
|
2742
|
+
}))) handleInsert(editor, payload, dataSource);
|
|
1893
2743
|
return false;
|
|
1894
2744
|
}, COMMAND_PRIORITY_EDITOR));
|
|
1895
2745
|
}
|
|
@@ -1942,9 +2792,32 @@ function handleRemove(editor, key, delay) {
|
|
|
1942
2792
|
const node = $getNodeByKey(key);
|
|
1943
2793
|
if (!node) return;
|
|
1944
2794
|
if (!delay) {
|
|
2795
|
+
if ($isTableCellNode(node)) {
|
|
2796
|
+
const table = $getTableForCell(node);
|
|
2797
|
+
const columnIndex = $getTableCellColumnIndex(node);
|
|
2798
|
+
const span = node.getColSpan();
|
|
2799
|
+
node.remove();
|
|
2800
|
+
if (table && columnIndex >= 0) $shrinkTableWidthsAfterCellRemoval(table, columnIndex, span);
|
|
2801
|
+
return;
|
|
2802
|
+
}
|
|
1945
2803
|
node.remove();
|
|
1946
2804
|
return;
|
|
1947
2805
|
}
|
|
2806
|
+
if ($isTableCellNode(node)) {
|
|
2807
|
+
const table = $getTableForCell(node);
|
|
2808
|
+
const columnIndex = $getTableCellColumnIndex(node);
|
|
2809
|
+
if (!table || columnIndex < 0) {
|
|
2810
|
+
logger$7.error(`❌ Table cell ${node.getKey()} is not attached to a valid table row.`);
|
|
2811
|
+
return;
|
|
2812
|
+
}
|
|
2813
|
+
const changeId = `${table.getKey()}:column:${columnIndex}`;
|
|
2814
|
+
node.replace($createTableCellDiffFromCell(editor, node, "remove", changeId), false);
|
|
2815
|
+
return;
|
|
2816
|
+
}
|
|
2817
|
+
if ($isTableRowNode(node) && $isTableNode(node.getParent())) {
|
|
2818
|
+
node.replace($createTableRowDiffFromRow(editor, node, "remove"), false);
|
|
2819
|
+
return;
|
|
2820
|
+
}
|
|
1948
2821
|
if (node.isInline() === false) {
|
|
1949
2822
|
const originDiffNode = $closest(node, (node) => node.getType() === DiffNode.getType());
|
|
1950
2823
|
if (originDiffNode) {
|
|
@@ -2006,6 +2879,37 @@ function handleInsert(editor, payload, dataSource) {
|
|
|
2006
2879
|
else referenceNode = $getNodeByKey(charToId(payload.afterId));
|
|
2007
2880
|
if (!referenceNode) throw new Error("Reference node not found for insertion.");
|
|
2008
2881
|
const newNodes = inode.root.children.map((child) => $parseSerializedNodeImpl(child, editor));
|
|
2882
|
+
const referencesTableCell = $isTableCellNode(referenceNode);
|
|
2883
|
+
const insertsOnlyTableCells = newNodes.length > 0 && newNodes.every($isTableCellNode);
|
|
2884
|
+
if (referencesTableCell || insertsOnlyTableCells) {
|
|
2885
|
+
if (!referencesTableCell || !insertsOnlyTableCells) {
|
|
2886
|
+
logger$7.error("❌ Table cells can only be inserted next to another cell in the same row.");
|
|
2887
|
+
return;
|
|
2888
|
+
}
|
|
2889
|
+
const cellReference = referenceNode;
|
|
2890
|
+
const table = $getTableForCell(cellReference);
|
|
2891
|
+
const referenceIndex = $getTableCellColumnIndex(cellReference);
|
|
2892
|
+
if (!table || referenceIndex < 0) {
|
|
2893
|
+
logger$7.error("❌ Table cell insertion requires a valid table parent.");
|
|
2894
|
+
return;
|
|
2895
|
+
}
|
|
2896
|
+
const rowWidthBefore = $getLogicalRowWidth(cellReference);
|
|
2897
|
+
const insertionIndex = isBefore ? referenceIndex : referenceIndex + cellReference.getColSpan();
|
|
2898
|
+
$updateTableWidthsForCellInsertion(table, rowWidthBefore, insertionIndex, newNodes.reduce((total, node) => total + ($isTableCellNode(node) ? node.getColSpan() : 0), 0));
|
|
2899
|
+
let spanOffset = 0;
|
|
2900
|
+
const cells = newNodes.map((node) => {
|
|
2901
|
+
const result = delay ? $createTableCellDiffFromCell(editor, node, "add", `${table.getKey()}:column:${insertionIndex + spanOffset}`) : node;
|
|
2902
|
+
spanOffset += node.getColSpan();
|
|
2903
|
+
return result;
|
|
2904
|
+
});
|
|
2905
|
+
if (isBefore) cells.reverse().forEach((cell) => {
|
|
2906
|
+
referenceNode = referenceNode.insertBefore(cell);
|
|
2907
|
+
});
|
|
2908
|
+
else cells.forEach((cell) => {
|
|
2909
|
+
referenceNode = referenceNode.insertAfter(cell);
|
|
2910
|
+
});
|
|
2911
|
+
return;
|
|
2912
|
+
}
|
|
2009
2913
|
if (!delay) {
|
|
2010
2914
|
if (isBefore) newNodes.reverse().forEach((node) => {
|
|
2011
2915
|
referenceNode = referenceNode.insertBefore(node);
|
|
@@ -2015,6 +2919,25 @@ function handleInsert(editor, payload, dataSource) {
|
|
|
2015
2919
|
});
|
|
2016
2920
|
return;
|
|
2017
2921
|
}
|
|
2922
|
+
const referencesTableRow = $isTableRowNode(referenceNode);
|
|
2923
|
+
const insertsOnlyTableRows = newNodes.every($isTableRowNode);
|
|
2924
|
+
if (referencesTableRow || insertsOnlyTableRows) {
|
|
2925
|
+
if (!referencesTableRow || !insertsOnlyTableRows || !$isTableNode(referenceNode.getParent())) {
|
|
2926
|
+
logger$7.error("❌ Table rows can only be inserted next to another row in the same table.");
|
|
2927
|
+
return;
|
|
2928
|
+
}
|
|
2929
|
+
if (isBefore) newNodes.reverse().forEach((node) => {
|
|
2930
|
+
if (!$isTableRowNode(node)) return;
|
|
2931
|
+
const diffRow = $createTableRowDiffFromRow(editor, node, "add");
|
|
2932
|
+
referenceNode = referenceNode.insertBefore(diffRow);
|
|
2933
|
+
});
|
|
2934
|
+
else newNodes.forEach((node) => {
|
|
2935
|
+
if (!$isTableRowNode(node)) return;
|
|
2936
|
+
const diffRow = $createTableRowDiffFromRow(editor, node, "add");
|
|
2937
|
+
referenceNode = referenceNode.insertAfter(diffRow);
|
|
2938
|
+
});
|
|
2939
|
+
return;
|
|
2940
|
+
}
|
|
2018
2941
|
if (isBefore) {
|
|
2019
2942
|
if (referenceNode.isInline() === false) {
|
|
2020
2943
|
const originDiffNode = $closest(referenceNode, (node) => node.getType() === DiffNode.getType());
|
|
@@ -2076,11 +2999,69 @@ function handleInsert(editor, payload, dataSource) {
|
|
|
2076
2999
|
}
|
|
2077
3000
|
//#endregion
|
|
2078
3001
|
//#region src/plugins/litexml/command/diffCommand.ts
|
|
2079
|
-
function
|
|
3002
|
+
function doTableRowAction(editor, node, action) {
|
|
3003
|
+
const pair = $getTableRowDiffPair(node);
|
|
3004
|
+
if (pair) {
|
|
3005
|
+
const survivingRow = action === 1 ? pair.add : pair.remove;
|
|
3006
|
+
const discardedRow = action === 1 ? pair.remove : pair.add;
|
|
3007
|
+
const plainRow = $createPlainTableRowFromDiff(editor, survivingRow);
|
|
3008
|
+
discardedRow.remove();
|
|
3009
|
+
survivingRow.replace(plainRow, false);
|
|
3010
|
+
plainRow.selectStart();
|
|
3011
|
+
return;
|
|
3012
|
+
}
|
|
3013
|
+
if (node.getDiffType() === "remove") {
|
|
3014
|
+
if (action === 1) node.remove();
|
|
3015
|
+
else {
|
|
3016
|
+
const plainRow = $createPlainTableRowFromDiff(editor, node);
|
|
3017
|
+
node.replace(plainRow, false);
|
|
3018
|
+
plainRow.selectStart();
|
|
3019
|
+
}
|
|
3020
|
+
return;
|
|
3021
|
+
}
|
|
3022
|
+
if (action === 1) {
|
|
3023
|
+
const plainRow = $createPlainTableRowFromDiff(editor, node);
|
|
3024
|
+
node.replace(plainRow, false);
|
|
3025
|
+
plainRow.selectStart();
|
|
3026
|
+
} else node.remove();
|
|
3027
|
+
}
|
|
3028
|
+
function doTableCellAction(editor, node, action) {
|
|
3029
|
+
const group = $getTableCellDiffGroup(node);
|
|
3030
|
+
const table = $getTableForCell(node);
|
|
3031
|
+
const columnIndex = $getTableCellColumnIndex(node);
|
|
3032
|
+
const span = node.getColSpan();
|
|
3033
|
+
if (node.getDiffType() === "add" && action === 1 || node.getDiffType() === "remove" && action === 0) {
|
|
3034
|
+
group.forEach((cell) => cell.replace($createPlainTableCellFromDiff(editor, cell), false));
|
|
3035
|
+
return;
|
|
3036
|
+
}
|
|
3037
|
+
if (table && columnIndex >= 0) {
|
|
3038
|
+
const widthCount = table.getColWidths()?.length;
|
|
3039
|
+
$removeTableWidthsForCompleteCellGroup(table, group, columnIndex, span);
|
|
3040
|
+
group.forEach((cell) => cell.remove());
|
|
3041
|
+
if (table.getColWidths()?.length === widthCount) $shrinkTableWidthsAfterCellRemoval(table, columnIndex, span);
|
|
3042
|
+
return;
|
|
3043
|
+
}
|
|
3044
|
+
group.forEach((cell) => cell.remove());
|
|
3045
|
+
}
|
|
3046
|
+
function doAction(editor, node, action) {
|
|
3047
|
+
if ($isTableRowDiffNode(node)) {
|
|
3048
|
+
doTableRowAction(editor, node, action);
|
|
3049
|
+
return;
|
|
3050
|
+
}
|
|
3051
|
+
const parent = node.getParent();
|
|
3052
|
+
if ($isTableCellDiffNode(parent)) {
|
|
3053
|
+
doTableCellAction(editor, parent, action);
|
|
3054
|
+
return;
|
|
3055
|
+
}
|
|
2080
3056
|
if (node.diffType === "modify") {
|
|
2081
3057
|
const children = node.getChildren();
|
|
2082
|
-
|
|
2083
|
-
|
|
3058
|
+
const selectedChild = action === 1 ? children[1] : children[0];
|
|
3059
|
+
if ($isDiffContentNode(selectedChild)) {
|
|
3060
|
+
const parent = node.getParentOrThrow();
|
|
3061
|
+
selectedChild.getChildren().forEach((child) => node.insertBefore(child));
|
|
3062
|
+
node.remove();
|
|
3063
|
+
if ($isElementNode(parent)) parent.selectEnd();
|
|
3064
|
+
} else if (selectedChild) node.replace(selectedChild, false).selectEnd();
|
|
2084
3065
|
}
|
|
2085
3066
|
if (node.diffType === "remove") {
|
|
2086
3067
|
if (action === 1) node.remove();
|
|
@@ -2092,7 +3073,10 @@ function doAction(node, action) {
|
|
|
2092
3073
|
if (node.diffType === "add") {
|
|
2093
3074
|
if (action === 1) {
|
|
2094
3075
|
const children = node.getChildren();
|
|
2095
|
-
node.
|
|
3076
|
+
const parent = node.getParentOrThrow();
|
|
3077
|
+
children.forEach((child) => node.insertBefore(child));
|
|
3078
|
+
node.remove();
|
|
3079
|
+
if ($isElementNode(parent)) parent.selectEnd();
|
|
2096
3080
|
} else if (action === 0) node.remove();
|
|
2097
3081
|
}
|
|
2098
3082
|
if (node.diffType === "listItemModify") {
|
|
@@ -2138,23 +3122,37 @@ function doAction(node, action) {
|
|
|
2138
3122
|
function registerLiteXMLDiffCommand(editor) {
|
|
2139
3123
|
return mergeRegister(editor.registerCommand(LITEXML_DIFFNODE_COMMAND, (payload) => {
|
|
2140
3124
|
const { action, nodeKey } = payload;
|
|
2141
|
-
const node = editor.getEditorState().read(() => {
|
|
2142
|
-
return $getNodeByKey(nodeKey);
|
|
2143
|
-
});
|
|
2144
|
-
if (!node) return false;
|
|
2145
3125
|
editor.update(() => {
|
|
2146
|
-
|
|
3126
|
+
const node = $getNodeByKey(nodeKey);
|
|
3127
|
+
if (!(node instanceof DiffNode) && !$isTableRowDiffNode(node)) return;
|
|
3128
|
+
doAction(editor, node, action);
|
|
2147
3129
|
});
|
|
2148
3130
|
return false;
|
|
2149
3131
|
}, COMMAND_PRIORITY_EDITOR), editor.registerCommand(LITEXML_DIFFNODE_ALL_COMMAND, (payload) => {
|
|
2150
3132
|
const { action } = payload;
|
|
2151
|
-
const
|
|
2152
|
-
return Array.from(editor._editorState._nodeMap.values()).filter((
|
|
3133
|
+
const nodeKeys = editor.getEditorState().read(() => {
|
|
3134
|
+
return Array.from(editor._editorState._nodeMap.values()).filter((node) => (node instanceof DiffNode || $isTableRowDiffNode(node)) && !!node.getParent()).map((node) => node.getKey());
|
|
2153
3135
|
});
|
|
2154
|
-
if (!
|
|
3136
|
+
if (!nodeKeys.length) return false;
|
|
2155
3137
|
editor.update(() => {
|
|
2156
|
-
|
|
2157
|
-
|
|
3138
|
+
const handled = /* @__PURE__ */ new Set();
|
|
3139
|
+
nodeKeys.forEach((nodeKey) => {
|
|
3140
|
+
if (handled.has(nodeKey)) return;
|
|
3141
|
+
const node = $getNodeByKey(nodeKey);
|
|
3142
|
+
if (!(node instanceof DiffNode) && !$isTableRowDiffNode(node)) return;
|
|
3143
|
+
const pair = $isTableRowDiffNode(node) ? $getTableRowDiffPair(node) : null;
|
|
3144
|
+
if (pair) {
|
|
3145
|
+
handled.add(pair.remove.getKey());
|
|
3146
|
+
handled.add(pair.add.getKey());
|
|
3147
|
+
} else {
|
|
3148
|
+
const parent = node instanceof DiffNode ? node.getParent() : null;
|
|
3149
|
+
if ($isTableCellDiffNode(parent)) $getTableCellDiffGroup(parent).forEach((cell) => {
|
|
3150
|
+
const diff = cell.getFirstChild();
|
|
3151
|
+
if (diff instanceof DiffNode) handled.add(diff.getKey());
|
|
3152
|
+
});
|
|
3153
|
+
handled.add(nodeKey);
|
|
3154
|
+
}
|
|
3155
|
+
doAction(editor, node, action);
|
|
2158
3156
|
});
|
|
2159
3157
|
});
|
|
2160
3158
|
return false;
|
|
@@ -2587,8 +3585,29 @@ function convertMdastToLexical(node, index, ctx, markdownReaders = {}, parentTyp
|
|
|
2587
3585
|
return ret;
|
|
2588
3586
|
}
|
|
2589
3587
|
if (child.value.startsWith("<!--") && child.value.endsWith("-->")) return ret;
|
|
2590
|
-
const tag = child.value
|
|
3588
|
+
const tag = getHtmlTagName$1(child.value);
|
|
2591
3589
|
const isEndTag = child.value.startsWith("</");
|
|
3590
|
+
const pairedTag = child.value.match(/^<\s*([a-z0-9-]+)\b[^>]*>([\s\S]*)<\/\s*\1\s*>$/i);
|
|
3591
|
+
if (!isEndTag && pairedTag) {
|
|
3592
|
+
const reader = markdownReaders["html"];
|
|
3593
|
+
const htmlChildren = pairedTag[2] ? [INodeHelper.createTextNode(pairedTag[2])] : [];
|
|
3594
|
+
if (Array.isArray(reader)) for (const element of reader) {
|
|
3595
|
+
const inode = element(child, htmlChildren, index);
|
|
3596
|
+
if (inode) {
|
|
3597
|
+
ret.push(inode);
|
|
3598
|
+
return ret;
|
|
3599
|
+
}
|
|
3600
|
+
}
|
|
3601
|
+
else if (typeof reader === "function") {
|
|
3602
|
+
const inode = reader(child, htmlChildren, index);
|
|
3603
|
+
if (inode) {
|
|
3604
|
+
ret.push(inode);
|
|
3605
|
+
return ret;
|
|
3606
|
+
}
|
|
3607
|
+
}
|
|
3608
|
+
ret.push(INodeHelper.createTextNode(child.value));
|
|
3609
|
+
return ret;
|
|
3610
|
+
}
|
|
2592
3611
|
if (selfClosingHtmlTags.has(tag)) {
|
|
2593
3612
|
const reader = markdownReaders["html"];
|
|
2594
3613
|
if (Array.isArray(reader)) for (const element of reader) {
|
|
@@ -2675,6 +3694,9 @@ function convertMdastToLexical(node, index, ctx, markdownReaders = {}, parentTyp
|
|
|
2675
3694
|
}
|
|
2676
3695
|
}
|
|
2677
3696
|
}
|
|
3697
|
+
function getHtmlTagName$1(value) {
|
|
3698
|
+
return value.match(/^<\/?\s*([a-z0-9-]+)/i)?.[1]?.toLowerCase() || value.replaceAll(/^<\/?|>$/g, "");
|
|
3699
|
+
}
|
|
2678
3700
|
function registerDefaultReaders(markdownReaders) {
|
|
2679
3701
|
if (!markdownReaders["root"]) markdownReaders["root"] = (node, children) => {
|
|
2680
3702
|
return {
|
|
@@ -3552,6 +4574,7 @@ function detectCodeLanguage(code) {
|
|
|
3552
4574
|
//#region src/plugins/markdown/plugin/index.ts
|
|
3553
4575
|
const DEFAULT_PASTE_MARKDOWN_AUTO_CONVERT_THRESHOLD = 5;
|
|
3554
4576
|
const RICH_HTML_SELECTOR = "strong,em,b,i,h1,h2,h3,h4,h5,h6,ul,ol,table,img,blockquote,pre>code,a[href]";
|
|
4577
|
+
const normalizeMarkdownForClipboard = (markdown) => markdown.replace(/\r?\n$/, "");
|
|
3555
4578
|
const MARKDOWN_DETECTION_RULES = [
|
|
3556
4579
|
{
|
|
3557
4580
|
name: "headers",
|
|
@@ -3748,8 +4771,9 @@ const MarkdownPlugin = class extends KernelPlugin {
|
|
|
3748
4771
|
if (!clipboardData) return false;
|
|
3749
4772
|
const selection = $getSelection();
|
|
3750
4773
|
if (!selection || selection.isCollapsed()) return false;
|
|
3751
|
-
const
|
|
3752
|
-
if (!
|
|
4774
|
+
const rawMarkdown = this.markdownDataSource.write(editor, { selection: true });
|
|
4775
|
+
if (!rawMarkdown) return false;
|
|
4776
|
+
const markdown = normalizeMarkdownForClipboard(rawMarkdown);
|
|
3753
4777
|
event.preventDefault();
|
|
3754
4778
|
const data = $getClipboardDataFromSelection(selection);
|
|
3755
4779
|
setLexicalClipboardDataTransfer(clipboardData, data);
|
|
@@ -3835,14 +4859,23 @@ const LitexmlPlugin = class extends KernelPlugin {
|
|
|
3835
4859
|
super();
|
|
3836
4860
|
this.kernel = kernel;
|
|
3837
4861
|
this.config = config;
|
|
3838
|
-
kernel.registerThemes({
|
|
4862
|
+
kernel.registerThemes({
|
|
4863
|
+
diffNode: config?.theme || "editor_diffNode",
|
|
4864
|
+
tableRowDiff: config?.tableRowDiffTheme || "editor_tableRowDiff",
|
|
4865
|
+
tableCellDiff: config?.tableCellDiffTheme || "editor_tableCellDiff"
|
|
4866
|
+
});
|
|
3839
4867
|
const litexmlService = new LitexmlService();
|
|
3840
4868
|
this.service = litexmlService;
|
|
3841
4869
|
kernel.registerService(ILitexmlService, litexmlService);
|
|
3842
4870
|
this.datasource = new LitexmlDataSource("litexml", (serviceId) => {
|
|
3843
4871
|
return kernel.requireService(serviceId);
|
|
3844
4872
|
}, litexmlService);
|
|
3845
|
-
kernel.registerNodes([
|
|
4873
|
+
kernel.registerNodes([
|
|
4874
|
+
DiffNode,
|
|
4875
|
+
DiffContentNode,
|
|
4876
|
+
TableRowDiffNode,
|
|
4877
|
+
TableCellDiffNode
|
|
4878
|
+
]);
|
|
3846
4879
|
kernel.registerDecorator(DiffNode.getType(), {
|
|
3847
4880
|
queryDOM: (el) => el.querySelector(".toolbar"),
|
|
3848
4881
|
render: (node, editor) => {
|
|
@@ -3854,6 +4887,8 @@ const LitexmlPlugin = class extends KernelPlugin {
|
|
|
3854
4887
|
onInit(editor) {
|
|
3855
4888
|
this.register(registerLiteXMLCommand(editor, this.datasource));
|
|
3856
4889
|
this.register(registerLiteXMLDiffCommand(editor));
|
|
4890
|
+
this.register(registerLegacyTableCellDiffNormalization(editor));
|
|
4891
|
+
this.register(registerTableRowDiffNormalization(editor));
|
|
3857
4892
|
this.register(editor.registerNodeTransform(DiffNode, (node) => {
|
|
3858
4893
|
if (node.diffType === "modify" && node.getChildrenSize() === 1) node.setDiffType("remove");
|
|
3859
4894
|
}));
|
|
@@ -3878,11 +4913,14 @@ const LitexmlPlugin = class extends KernelPlugin {
|
|
|
3878
4913
|
const diffNode = node;
|
|
3879
4914
|
const lines = [];
|
|
3880
4915
|
switch (diffNode.diffType) {
|
|
3881
|
-
case "modify":
|
|
3882
|
-
|
|
4916
|
+
case "modify": {
|
|
4917
|
+
const after = diffNode.getChildAtIndex(1);
|
|
4918
|
+
if ($isDiffContentNode(after)) after.getChildren().forEach((child) => nodeToXML(child, lines, indent));
|
|
4919
|
+
else nodeToXML(after, lines, indent);
|
|
3883
4920
|
break;
|
|
4921
|
+
}
|
|
3884
4922
|
case "add":
|
|
3885
|
-
|
|
4923
|
+
diffNode.getChildren().forEach((child) => nodeToXML(child, lines, indent));
|
|
3886
4924
|
break;
|
|
3887
4925
|
case "remove": break;
|
|
3888
4926
|
case "listItemModify": diffNode.getChildAtIndex(1).getChildren().forEach((child) => {
|
|
@@ -3891,16 +4929,49 @@ const LitexmlPlugin = class extends KernelPlugin {
|
|
|
3891
4929
|
}
|
|
3892
4930
|
return { lines };
|
|
3893
4931
|
});
|
|
4932
|
+
this.service.registerXMLWriter(TableRowDiffNode.getType(), (node, ctx) => {
|
|
4933
|
+
if (!$isTableRowDiffNode(node)) return false;
|
|
4934
|
+
if (node.getDiffType() === "remove") return { lines: [] };
|
|
4935
|
+
return ctx.createXmlNode("tr", {});
|
|
4936
|
+
});
|
|
4937
|
+
this.service.registerXMLWriter(TableCellDiffNode.getType(), (node, ctx) => {
|
|
4938
|
+
if (!$isTableCellDiffNode(node)) return false;
|
|
4939
|
+
if (node.getDiffType() === "remove") return { lines: [] };
|
|
4940
|
+
const attributes = {};
|
|
4941
|
+
if (node.getColSpan() > 1) attributes.colSpan = String(node.getColSpan());
|
|
4942
|
+
if (node.getRowSpan() > 1) attributes.rowSpan = String(node.getRowSpan());
|
|
4943
|
+
if (node.getBackgroundColor()) attributes.backgroundColor = node.getBackgroundColor();
|
|
4944
|
+
return ctx.createXmlNode("td", attributes);
|
|
4945
|
+
});
|
|
3894
4946
|
}
|
|
3895
4947
|
registerMarkdown() {
|
|
3896
|
-
this.kernel.requireService(IMarkdownShortCutService)
|
|
4948
|
+
const markdownService = this.kernel.requireService(IMarkdownShortCutService);
|
|
4949
|
+
markdownService?.registerMarkdownWriter(TableCellDiffNode.getType(), (ctx, node) => {
|
|
4950
|
+
if (!$isTableCellDiffNode(node)) return false;
|
|
4951
|
+
if (node.getDiffType() === "remove") return true;
|
|
4952
|
+
if (!node.getNextSibling()) ctx.wrap("|", "|");
|
|
4953
|
+
else ctx.wrap("|", "");
|
|
4954
|
+
return true;
|
|
4955
|
+
});
|
|
4956
|
+
markdownService?.registerMarkdownWriter(TableRowDiffNode.getType(), (ctx, node) => {
|
|
4957
|
+
if (!$isTableRowDiffNode(node)) return false;
|
|
4958
|
+
if (node.getDiffType() === "remove") return true;
|
|
4959
|
+
const parent = node.getParent();
|
|
4960
|
+
if (!$isTableNode(parent)) return true;
|
|
4961
|
+
if (node.getPreviousSiblings().some((sibling) => !$isTableRowDiffNode(sibling) || sibling.getDiffType() !== "remove")) ctx.wrap("", "\n");
|
|
4962
|
+
else ctx.wrap("", `\n${parent.getColWidths()?.map(() => "|:--").join("")}|\n`);
|
|
4963
|
+
});
|
|
4964
|
+
markdownService?.registerMarkdownWriter(DiffNode.getType(), (ctx, node) => {
|
|
3897
4965
|
const diffNode = node;
|
|
3898
4966
|
switch (diffNode.diffType) {
|
|
3899
|
-
case "modify":
|
|
3900
|
-
|
|
4967
|
+
case "modify": {
|
|
4968
|
+
const after = diffNode.getChildAtIndex(1);
|
|
4969
|
+
if ($isDiffContentNode(after)) after.getChildren().forEach((child) => ctx.processChild(ctx, child));
|
|
4970
|
+
else ctx.processChild(ctx, after);
|
|
3901
4971
|
break;
|
|
4972
|
+
}
|
|
3902
4973
|
case "add":
|
|
3903
|
-
ctx.processChild(ctx,
|
|
4974
|
+
diffNode.getChildren().forEach((child) => ctx.processChild(ctx, child));
|
|
3904
4975
|
break;
|
|
3905
4976
|
case "remove": break;
|
|
3906
4977
|
case "listItemModify": diffNode.getChildAtIndex(1).getChildren().forEach((child) => {
|
|
@@ -4414,6 +5485,84 @@ function runPasteHandlers(ctx, handlers) {
|
|
|
4414
5485
|
return false;
|
|
4415
5486
|
}
|
|
4416
5487
|
//#endregion
|
|
5488
|
+
//#region src/plugins/common/plugin/progressive-select-all.ts
|
|
5489
|
+
const COLLAPSIBLE_NODE_TYPE = "collapsible";
|
|
5490
|
+
function registerProgressiveSelectAll(editor) {
|
|
5491
|
+
return editor.registerCommand(SELECT_ALL_COMMAND, () => {
|
|
5492
|
+
const selection = $getSelection();
|
|
5493
|
+
if ($isTableSelection(selection)) return $expandTableSelection(selection);
|
|
5494
|
+
if (!$isRangeSelection(selection)) return false;
|
|
5495
|
+
const anchorCell = $findNearestNode(selection.anchor.getNode(), $isTableCellNode);
|
|
5496
|
+
const focusCell = $findNearestNode(selection.focus.getNode(), $isTableCellNode);
|
|
5497
|
+
if (anchorCell && focusCell && anchorCell.is(focusCell)) {
|
|
5498
|
+
const table = $findNearestNode(anchorCell, $isTableNode);
|
|
5499
|
+
if (table) {
|
|
5500
|
+
const tableSelection = $createTableSelection();
|
|
5501
|
+
tableSelection.set(table.getKey(), anchorCell.getKey(), anchorCell.getKey());
|
|
5502
|
+
$setSelection(tableSelection);
|
|
5503
|
+
return true;
|
|
5504
|
+
}
|
|
5505
|
+
}
|
|
5506
|
+
const anchorCollapsible = $findCollapsibleAncestor(selection.anchor.getNode());
|
|
5507
|
+
const focusCollapsible = $findCollapsibleAncestor(selection.focus.getNode());
|
|
5508
|
+
if (!anchorCollapsible || !focusCollapsible || !anchorCollapsible.is(focusCollapsible)) return false;
|
|
5509
|
+
if ($isEntireElementSelected(selection, anchorCollapsible)) $selectDocument();
|
|
5510
|
+
else anchorCollapsible.select(0, anchorCollapsible.getChildrenSize());
|
|
5511
|
+
return true;
|
|
5512
|
+
}, COMMAND_PRIORITY_HIGH);
|
|
5513
|
+
}
|
|
5514
|
+
function $expandTableSelection(selection) {
|
|
5515
|
+
const table = $getNodeByKey(selection.tableKey);
|
|
5516
|
+
if (!$isTableNode(table)) return false;
|
|
5517
|
+
const cells = $getTableCells(table);
|
|
5518
|
+
if (!cells) return false;
|
|
5519
|
+
const selectedCellKeys = new Set(selection.getNodes().filter($isTableCellNode).map((cell) => cell.getKey()));
|
|
5520
|
+
if (cells.keys.every((key) => selectedCellKeys.has(key))) {
|
|
5521
|
+
$selectDocument();
|
|
5522
|
+
return true;
|
|
5523
|
+
}
|
|
5524
|
+
const tableSelection = $createTableSelection();
|
|
5525
|
+
tableSelection.set(table.getKey(), cells.first.getKey(), cells.last.getKey());
|
|
5526
|
+
$setSelection(tableSelection);
|
|
5527
|
+
return true;
|
|
5528
|
+
}
|
|
5529
|
+
function $getTableCells(table) {
|
|
5530
|
+
if (!$isTableNode(table)) return null;
|
|
5531
|
+
const [tableMap] = $computeTableMapSkipCellCheck(table, null, null);
|
|
5532
|
+
const entries = tableMap.flat().filter(Boolean);
|
|
5533
|
+
const first = entries[0]?.cell;
|
|
5534
|
+
const last = entries.at(-1)?.cell;
|
|
5535
|
+
if (!first || !last) return null;
|
|
5536
|
+
return {
|
|
5537
|
+
first,
|
|
5538
|
+
keys: Array.from(new Set(entries.map(({ cell }) => cell.getKey()))),
|
|
5539
|
+
last
|
|
5540
|
+
};
|
|
5541
|
+
}
|
|
5542
|
+
function $findNearestNode(node, predicate) {
|
|
5543
|
+
let current = node;
|
|
5544
|
+
while (current) {
|
|
5545
|
+
if (predicate(current)) return current;
|
|
5546
|
+
current = current.getParent();
|
|
5547
|
+
}
|
|
5548
|
+
return null;
|
|
5549
|
+
}
|
|
5550
|
+
function $findCollapsibleAncestor(node) {
|
|
5551
|
+
return $findNearestNode(node, (candidate) => $isElementNode(candidate) && candidate.getType() === COLLAPSIBLE_NODE_TYPE);
|
|
5552
|
+
}
|
|
5553
|
+
function $isEntireElementSelected(selection, element) {
|
|
5554
|
+
const startOffset = 0;
|
|
5555
|
+
const endOffset = element.getChildrenSize();
|
|
5556
|
+
return $isPointAt(selection.anchor, element, startOffset) && $isPointAt(selection.focus, element, endOffset) || $isPointAt(selection.focus, element, startOffset) && $isPointAt(selection.anchor, element, endOffset);
|
|
5557
|
+
}
|
|
5558
|
+
function $isPointAt(point, element, offset) {
|
|
5559
|
+
return point.key === element.getKey() && point.offset === offset && point.type === "element";
|
|
5560
|
+
}
|
|
5561
|
+
function $selectDocument() {
|
|
5562
|
+
const root = $getRoot();
|
|
5563
|
+
root.select(0, root.getChildrenSize());
|
|
5564
|
+
}
|
|
5565
|
+
//#endregion
|
|
4417
5566
|
//#region src/plugins/common/plugin/register.ts
|
|
4418
5567
|
const logger$3 = createDebugLogger("plugin", "common");
|
|
4419
5568
|
function resolveElement(element, isBackward, focusOffset) {
|
|
@@ -4429,7 +5578,9 @@ function resolveElement(element, isBackward, focusOffset) {
|
|
|
4429
5578
|
block = parent;
|
|
4430
5579
|
}
|
|
4431
5580
|
}
|
|
4432
|
-
|
|
5581
|
+
const childIndex = isBackward ? offset - 1 : offset;
|
|
5582
|
+
if (childIndex < 0 || childIndex >= block.getChildrenSize()) return null;
|
|
5583
|
+
return block.getChildAtIndex(childIndex);
|
|
4433
5584
|
}
|
|
4434
5585
|
function isCodeNodeLastLine(focusNode) {
|
|
4435
5586
|
if (!$isCodeHighlightNode(focusNode)) return false;
|
|
@@ -4474,7 +5625,55 @@ function $isSelectionAtEndOfRoot(selection) {
|
|
|
4474
5625
|
const focus = selection.focus;
|
|
4475
5626
|
return focus.key === "root" && focus.offset === $getRoot().getChildrenSize();
|
|
4476
5627
|
}
|
|
4477
|
-
function
|
|
5628
|
+
function $isSelectionInList(selection) {
|
|
5629
|
+
return Boolean($closest(selection.focus.getNode(), (node) => $isListNode(node) || $isListItemNode(node)));
|
|
5630
|
+
}
|
|
5631
|
+
function $getLeadingQuoteNode(selection) {
|
|
5632
|
+
const anchor = selection.anchor;
|
|
5633
|
+
if (anchor.offset !== 0) return false;
|
|
5634
|
+
let node = anchor.getNode();
|
|
5635
|
+
let current = node;
|
|
5636
|
+
let quoteNode = null;
|
|
5637
|
+
while (current) {
|
|
5638
|
+
if ($isQuoteNode(current)) {
|
|
5639
|
+
quoteNode = current;
|
|
5640
|
+
break;
|
|
5641
|
+
}
|
|
5642
|
+
current = current.getParent();
|
|
5643
|
+
}
|
|
5644
|
+
if (!quoteNode) return false;
|
|
5645
|
+
while (node !== quoteNode) {
|
|
5646
|
+
if (node.getIndexWithinParent() !== 0) return false;
|
|
5647
|
+
const parent = node.getParent();
|
|
5648
|
+
if (!parent) return false;
|
|
5649
|
+
node = parent;
|
|
5650
|
+
}
|
|
5651
|
+
return quoteNode;
|
|
5652
|
+
}
|
|
5653
|
+
function $unwrapQuoteNode(quoteNode) {
|
|
5654
|
+
const children = quoteNode.getChildren();
|
|
5655
|
+
if (children.length === 0) {
|
|
5656
|
+
const paragraphNode = $createParagraphNode();
|
|
5657
|
+
quoteNode.replace(paragraphNode);
|
|
5658
|
+
paragraphNode.select(0, 0);
|
|
5659
|
+
return;
|
|
5660
|
+
}
|
|
5661
|
+
let firstNode = null;
|
|
5662
|
+
for (const child of children) {
|
|
5663
|
+
if ($isElementNode(child)) {
|
|
5664
|
+
quoteNode.insertBefore(child);
|
|
5665
|
+
firstNode ||= child;
|
|
5666
|
+
continue;
|
|
5667
|
+
}
|
|
5668
|
+
const paragraphNode = $createParagraphNode();
|
|
5669
|
+
paragraphNode.append(child);
|
|
5670
|
+
quoteNode.insertBefore(paragraphNode);
|
|
5671
|
+
firstNode ||= paragraphNode;
|
|
5672
|
+
}
|
|
5673
|
+
quoteNode.remove();
|
|
5674
|
+
if ($isElementNode(firstNode)) firstNode.select(0, 0);
|
|
5675
|
+
}
|
|
5676
|
+
function registerBlockBackspace(editor) {
|
|
4478
5677
|
return editor.registerCommand(KEY_BACKSPACE_COMMAND, (payload) => {
|
|
4479
5678
|
const headingNode = editor.getEditorState().read(() => {
|
|
4480
5679
|
const selection = $getSelection();
|
|
@@ -4502,6 +5701,22 @@ function registerHeaderBackspace(editor) {
|
|
|
4502
5701
|
});
|
|
4503
5702
|
return true;
|
|
4504
5703
|
}
|
|
5704
|
+
if (editor.getEditorState().read(() => {
|
|
5705
|
+
const selection = $getSelection();
|
|
5706
|
+
if (!$isRangeSelection(selection) || !selection.isCollapsed()) return false;
|
|
5707
|
+
return $getLeadingQuoteNode(selection);
|
|
5708
|
+
})) {
|
|
5709
|
+
payload.stopImmediatePropagation();
|
|
5710
|
+
payload.preventDefault();
|
|
5711
|
+
payload.stopPropagation();
|
|
5712
|
+
editor.update(() => {
|
|
5713
|
+
const selection = $getSelection();
|
|
5714
|
+
if (!$isRangeSelection(selection) || !selection.isCollapsed()) return;
|
|
5715
|
+
const leadingQuoteNode = $getLeadingQuoteNode(selection);
|
|
5716
|
+
if (leadingQuoteNode) $unwrapQuoteNode(leadingQuoteNode);
|
|
5717
|
+
});
|
|
5718
|
+
return true;
|
|
5719
|
+
}
|
|
4505
5720
|
return false;
|
|
4506
5721
|
}, COMMAND_PRIORITY_NORMAL);
|
|
4507
5722
|
}
|
|
@@ -4556,6 +5771,7 @@ function registerRichKeydown(editor, kernel, options) {
|
|
|
4556
5771
|
return true;
|
|
4557
5772
|
}
|
|
4558
5773
|
} else if ($isRangeSelection(selection)) {
|
|
5774
|
+
if ($isSelectionInList(selection)) return false;
|
|
4559
5775
|
const possibleNode = $getAdjacentNode(selection.focus, true);
|
|
4560
5776
|
const upblock = possibleNode || $getDownUpNode(selection.focus, true);
|
|
4561
5777
|
if (!event.shiftKey && $isDecoratorNode(possibleNode)) {
|
|
@@ -4625,6 +5841,7 @@ function registerRichKeydown(editor, kernel, options) {
|
|
|
4625
5841
|
event.preventDefault();
|
|
4626
5842
|
return true;
|
|
4627
5843
|
}
|
|
5844
|
+
if ($isSelectionInList(selection)) return false;
|
|
4628
5845
|
const possibleNode = $getAdjacentNode(selection.focus, false);
|
|
4629
5846
|
const upblock = possibleNode || $getDownUpNode(selection.focus, false);
|
|
4630
5847
|
if (!event.shiftKey && $isDecoratorNode(possibleNode)) {
|
|
@@ -4919,7 +6136,18 @@ const CommonPlugin = class extends KernelPlugin {
|
|
|
4919
6136
|
if (this.config?.pasteAsPlainText) return runPasteHandlers(ctx, [handleFilePaste, handlePlainTextPaste]);
|
|
4920
6137
|
return false;
|
|
4921
6138
|
}, COMMAND_PRIORITY_CRITICAL));
|
|
4922
|
-
|
|
6139
|
+
let dragonCleanup;
|
|
6140
|
+
const clearDragon = () => {
|
|
6141
|
+
dragonCleanup?.();
|
|
6142
|
+
dragonCleanup = void 0;
|
|
6143
|
+
};
|
|
6144
|
+
this.register(this.kernel.registerRootListener((rootElement) => {
|
|
6145
|
+
clearDragon();
|
|
6146
|
+
if (!rootElement || !CAN_USE_DOM) return;
|
|
6147
|
+
dragonCleanup = registerDragonSupport(editor);
|
|
6148
|
+
}));
|
|
6149
|
+
this.register(clearDragon);
|
|
6150
|
+
this.registerClears(registerRichText(editor), registerProgressiveSelectAll(editor), 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
6151
|
const selection = $getSelection();
|
|
4924
6152
|
if (!$isRangeSelection(selection)) return false;
|
|
4925
6153
|
const { inCell, inTable } = $isCursorInTable(selection);
|
|
@@ -5793,6 +7021,62 @@ function modeMatch(mode = "") {
|
|
|
5793
7021
|
return LANGUAGES.find((m) => m.value === mode || m.ext?.includes(mode))?.value || "plain";
|
|
5794
7022
|
}
|
|
5795
7023
|
//#endregion
|
|
7024
|
+
//#region src/plugins/codemirror-block/service/index.ts
|
|
7025
|
+
const ICodemirrorEditLockService = genServiceId("CodemirrorEditLockService");
|
|
7026
|
+
var CodemirrorEditLockService = class {
|
|
7027
|
+
constructor() {
|
|
7028
|
+
this.providerUnsubscribers = /* @__PURE__ */ new Map();
|
|
7029
|
+
this.providers = /* @__PURE__ */ new Set();
|
|
7030
|
+
this.subscribers = /* @__PURE__ */ new Set();
|
|
7031
|
+
}
|
|
7032
|
+
acquireLock(key, label) {
|
|
7033
|
+
if (this.getRemoteLockOwner(key)) return false;
|
|
7034
|
+
const acquiredProviders = [];
|
|
7035
|
+
for (const provider of this.providers) {
|
|
7036
|
+
if (!provider.acquireLock(key, label)) {
|
|
7037
|
+
acquiredProviders.forEach((acquiredProvider) => acquiredProvider.releaseLock(key));
|
|
7038
|
+
this.notify();
|
|
7039
|
+
return false;
|
|
7040
|
+
}
|
|
7041
|
+
acquiredProviders.push(provider);
|
|
7042
|
+
}
|
|
7043
|
+
this.notify();
|
|
7044
|
+
return true;
|
|
7045
|
+
}
|
|
7046
|
+
getRemoteLockOwner(key) {
|
|
7047
|
+
for (const provider of this.providers) {
|
|
7048
|
+
const owner = provider.getRemoteLockOwner(key);
|
|
7049
|
+
if (owner) return owner;
|
|
7050
|
+
}
|
|
7051
|
+
return null;
|
|
7052
|
+
}
|
|
7053
|
+
registerProvider(provider) {
|
|
7054
|
+
this.providers.add(provider);
|
|
7055
|
+
this.providerUnsubscribers.set(provider, provider.subscribe(() => this.notify()));
|
|
7056
|
+
this.notify();
|
|
7057
|
+
return () => {
|
|
7058
|
+
this.providerUnsubscribers.get(provider)?.();
|
|
7059
|
+
this.providerUnsubscribers.delete(provider);
|
|
7060
|
+
this.providers.delete(provider);
|
|
7061
|
+
this.notify();
|
|
7062
|
+
};
|
|
7063
|
+
}
|
|
7064
|
+
releaseLock(key) {
|
|
7065
|
+
this.providers.forEach((provider) => provider.releaseLock(key));
|
|
7066
|
+
this.notify();
|
|
7067
|
+
}
|
|
7068
|
+
subscribe(listener) {
|
|
7069
|
+
this.subscribers.add(listener);
|
|
7070
|
+
listener();
|
|
7071
|
+
return () => {
|
|
7072
|
+
this.subscribers.delete(listener);
|
|
7073
|
+
};
|
|
7074
|
+
}
|
|
7075
|
+
notify() {
|
|
7076
|
+
this.subscribers.forEach((listener) => listener());
|
|
7077
|
+
}
|
|
7078
|
+
};
|
|
7079
|
+
//#endregion
|
|
5796
7080
|
//#region src/plugins/codemirror-block/plugin/index.ts
|
|
5797
7081
|
const CodemirrorPlugin = class extends KernelPlugin {
|
|
5798
7082
|
static {
|
|
@@ -5803,6 +7087,7 @@ const CodemirrorPlugin = class extends KernelPlugin {
|
|
|
5803
7087
|
this.kernel = kernel;
|
|
5804
7088
|
kernel.registerNodes([CodeMirrorNode]);
|
|
5805
7089
|
kernel.registerThemes({ hr: config?.theme || "" });
|
|
7090
|
+
kernel.registerServiceHotReload(ICodemirrorEditLockService, new CodemirrorEditLockService());
|
|
5806
7091
|
this.registerDecorator(kernel, "codemirror", (node, editor) => {
|
|
5807
7092
|
return config?.decorator ? config.decorator(node, editor) : null;
|
|
5808
7093
|
});
|
|
@@ -6762,62 +8047,320 @@ const ImagePlugin = class extends KernelPlugin {
|
|
|
6762
8047
|
}
|
|
6763
8048
|
};
|
|
6764
8049
|
//#endregion
|
|
6765
|
-
//#region src/plugins/link/
|
|
6766
|
-
const
|
|
6767
|
-
const
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
8050
|
+
//#region src/plugins/link/service/i-link-service.ts
|
|
8051
|
+
const ILinkService = genServiceId("LinkService");
|
|
8052
|
+
const DEFAULT_LABELS = {
|
|
8053
|
+
convertToBlockCard: "Convert to block card",
|
|
8054
|
+
convertToCard: "Convert to card",
|
|
8055
|
+
convertToIframe: "Convert to iframe",
|
|
8056
|
+
convertToLink: "Convert to link",
|
|
8057
|
+
convertToSchema: "Convert to schema"
|
|
8058
|
+
};
|
|
8059
|
+
var LinkProtocolPolicy = class {
|
|
8060
|
+
constructor() {
|
|
8061
|
+
this.allowedProtocols = /* @__PURE__ */ new Set([
|
|
8062
|
+
"http:",
|
|
8063
|
+
"https:",
|
|
8064
|
+
"mailto:",
|
|
8065
|
+
"sms:",
|
|
8066
|
+
"tel:"
|
|
8067
|
+
]);
|
|
6780
8068
|
}
|
|
6781
|
-
|
|
6782
|
-
return
|
|
6783
|
-
rel: node.__rel,
|
|
6784
|
-
target: node.__target,
|
|
6785
|
-
title: node.__title
|
|
6786
|
-
}, node.__key);
|
|
8069
|
+
getAllowedProtocols() {
|
|
8070
|
+
return this.allowedProtocols;
|
|
6787
8071
|
}
|
|
6788
|
-
|
|
6789
|
-
|
|
6790
|
-
const { target = null, rel = null, title = null } = attributes;
|
|
6791
|
-
this.__url = url;
|
|
6792
|
-
this.__target = target;
|
|
6793
|
-
this.__rel = rel;
|
|
6794
|
-
this.__title = title;
|
|
8072
|
+
setAllowedProtocols(protocols) {
|
|
8073
|
+
this.allowedProtocols = new Set(protocols.map(normalizeProtocol));
|
|
6795
8074
|
}
|
|
6796
|
-
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
this.
|
|
6800
|
-
|
|
6801
|
-
element.addEventListener("mouseenter", (event) => {
|
|
6802
|
-
if (event.target instanceof HTMLElement) {
|
|
6803
|
-
event.target.classList.add("hover");
|
|
6804
|
-
editor.dispatchCommand(HOVER_LINK_COMMAND, {
|
|
6805
|
-
event,
|
|
6806
|
-
linkNode: this
|
|
6807
|
-
});
|
|
6808
|
-
}
|
|
6809
|
-
});
|
|
6810
|
-
element.addEventListener("mouseleave", (event) => {
|
|
6811
|
-
if (event.target instanceof HTMLElement) {
|
|
6812
|
-
event.target.classList.remove("hover");
|
|
6813
|
-
editor.dispatchCommand(HOVER_OUT_LINK_COMMAND, { event });
|
|
6814
|
-
}
|
|
6815
|
-
});
|
|
6816
|
-
return element;
|
|
8075
|
+
};
|
|
8076
|
+
var LinkRuleRegistry = class {
|
|
8077
|
+
constructor() {
|
|
8078
|
+
this.embedRules = [];
|
|
8079
|
+
this.schemaRules = [];
|
|
6817
8080
|
}
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
|
|
8081
|
+
getEmbedRule(url, context) {
|
|
8082
|
+
return this.embedRules.find((rule) => rule.match(url, context)) || null;
|
|
8083
|
+
}
|
|
8084
|
+
getSchemaRule(url, context, parseSchemaUrl) {
|
|
8085
|
+
const schema = context.schema === void 0 ? parseSchemaUrl(url) : context.schema;
|
|
8086
|
+
return this.schemaRules.find((rule) => rule.match(url, {
|
|
8087
|
+
...context,
|
|
8088
|
+
schema
|
|
8089
|
+
})) || null;
|
|
8090
|
+
}
|
|
8091
|
+
getSchemaRuleById(schemaType) {
|
|
8092
|
+
return this.schemaRules.find((rule) => rule.id === schemaType) || null;
|
|
8093
|
+
}
|
|
8094
|
+
setEmbedRules(rules = []) {
|
|
8095
|
+
this.embedRules = rules;
|
|
8096
|
+
}
|
|
8097
|
+
setSchemaRules(rules = []) {
|
|
8098
|
+
this.schemaRules = rules;
|
|
8099
|
+
}
|
|
8100
|
+
};
|
|
8101
|
+
var LegacySchemaLinkRegistry = class {
|
|
8102
|
+
constructor() {
|
|
8103
|
+
this.protocols = /* @__PURE__ */ new Set();
|
|
8104
|
+
}
|
|
8105
|
+
hasSchemaLinkRenderer(url) {
|
|
8106
|
+
try {
|
|
8107
|
+
const { protocol } = new URL(url);
|
|
8108
|
+
return this.protocols.has(protocol);
|
|
8109
|
+
} catch {
|
|
8110
|
+
return false;
|
|
8111
|
+
}
|
|
8112
|
+
}
|
|
8113
|
+
setSchemaLinkRenderers(renderers = []) {
|
|
8114
|
+
this.protocols = new Set(renderers.map(({ protocol }) => normalizeProtocol(protocol)));
|
|
8115
|
+
}
|
|
8116
|
+
};
|
|
8117
|
+
var LinkToolbarController = class {
|
|
8118
|
+
constructor() {
|
|
8119
|
+
this.baseEnabled = true;
|
|
8120
|
+
this.labels = DEFAULT_LABELS;
|
|
8121
|
+
this.toolbarActions = [];
|
|
8122
|
+
this.toolbarSuppressions = /* @__PURE__ */ new Set();
|
|
8123
|
+
}
|
|
8124
|
+
get enabled() {
|
|
8125
|
+
return this.baseEnabled && this.toolbarSuppressions.size === 0;
|
|
8126
|
+
}
|
|
8127
|
+
getLabels() {
|
|
8128
|
+
return this.labels;
|
|
8129
|
+
}
|
|
8130
|
+
getToolbarActions(context) {
|
|
8131
|
+
return this.toolbarActions.filter((action) => action.visible?.(context) ?? true);
|
|
8132
|
+
}
|
|
8133
|
+
setLabels(labels) {
|
|
8134
|
+
this.labels = {
|
|
8135
|
+
...DEFAULT_LABELS,
|
|
8136
|
+
...labels
|
|
8137
|
+
};
|
|
8138
|
+
}
|
|
8139
|
+
setToolbarActions(actions = []) {
|
|
8140
|
+
this.toolbarActions = actions;
|
|
8141
|
+
}
|
|
8142
|
+
setEnabled(enable) {
|
|
8143
|
+
const previousEnabled = this.enabled;
|
|
8144
|
+
this.baseEnabled = enable;
|
|
8145
|
+
return previousEnabled !== this.enabled;
|
|
8146
|
+
}
|
|
8147
|
+
suppress(reason = "unknown") {
|
|
8148
|
+
const previousEnabled = this.enabled;
|
|
8149
|
+
const token = Symbol(reason);
|
|
8150
|
+
this.toolbarSuppressions.add(token);
|
|
8151
|
+
return {
|
|
8152
|
+
changed: previousEnabled !== this.enabled,
|
|
8153
|
+
token
|
|
8154
|
+
};
|
|
8155
|
+
}
|
|
8156
|
+
restore(token) {
|
|
8157
|
+
const previousEnabled = this.enabled;
|
|
8158
|
+
if (!this.toolbarSuppressions.delete(token)) return false;
|
|
8159
|
+
return previousEnabled !== this.enabled;
|
|
8160
|
+
}
|
|
8161
|
+
};
|
|
8162
|
+
var LinkService = class extends EventEmitter {
|
|
8163
|
+
constructor(..._args) {
|
|
8164
|
+
super(..._args);
|
|
8165
|
+
this.cardMetadataLoadingKeys = /* @__PURE__ */ new Set();
|
|
8166
|
+
this.protocolPolicy = new LinkProtocolPolicy();
|
|
8167
|
+
this.legacySchemaLinkRegistry = new LegacySchemaLinkRegistry();
|
|
8168
|
+
this.ruleRegistry = new LinkRuleRegistry();
|
|
8169
|
+
this.toolbarController = new LinkToolbarController();
|
|
8170
|
+
this.toolbarItems = /* @__PURE__ */ new Map();
|
|
8171
|
+
}
|
|
8172
|
+
get enableLinkToolbar() {
|
|
8173
|
+
return this.toolbarController.enabled;
|
|
8174
|
+
}
|
|
8175
|
+
getAllowedProtocols() {
|
|
8176
|
+
return this.protocolPolicy.getAllowedProtocols();
|
|
8177
|
+
}
|
|
8178
|
+
getEmbedRule(url, context) {
|
|
8179
|
+
return this.ruleRegistry.getEmbedRule(url, context);
|
|
8180
|
+
}
|
|
8181
|
+
getLabels() {
|
|
8182
|
+
return this.toolbarController.getLabels();
|
|
8183
|
+
}
|
|
8184
|
+
hasSchemaLinkRenderer(url) {
|
|
8185
|
+
return this.legacySchemaLinkRegistry.hasSchemaLinkRenderer(url);
|
|
8186
|
+
}
|
|
8187
|
+
isCardMetadataLoading(nodeKey) {
|
|
8188
|
+
return this.cardMetadataLoadingKeys.has(nodeKey);
|
|
8189
|
+
}
|
|
8190
|
+
setCardMetadataLoading(nodeKey, loading) {
|
|
8191
|
+
if (!(loading ? !this.cardMetadataLoadingKeys.has(nodeKey) : this.cardMetadataLoadingKeys.has(nodeKey))) return;
|
|
8192
|
+
if (loading) this.cardMetadataLoadingKeys.add(nodeKey);
|
|
8193
|
+
else this.cardMetadataLoadingKeys.delete(nodeKey);
|
|
8194
|
+
this.emit("cardMetadataChange");
|
|
8195
|
+
}
|
|
8196
|
+
getSchemaRule(url, context) {
|
|
8197
|
+
return this.ruleRegistry.getSchemaRule(url, context, (url) => this.parseSchemaUrl(url));
|
|
8198
|
+
}
|
|
8199
|
+
getToolbarActions(context) {
|
|
8200
|
+
return this.toolbarController.getToolbarActions(context);
|
|
8201
|
+
}
|
|
8202
|
+
parseSchemaUrl(url) {
|
|
8203
|
+
try {
|
|
8204
|
+
const parsedUrl = new URL(url);
|
|
8205
|
+
return {
|
|
8206
|
+
hash: parsedUrl.hash,
|
|
8207
|
+
host: parsedUrl.host,
|
|
8208
|
+
params: Object.fromEntries(parsedUrl.searchParams.entries()),
|
|
8209
|
+
pathname: parsedUrl.pathname,
|
|
8210
|
+
protocol: parsedUrl.protocol,
|
|
8211
|
+
raw: url,
|
|
8212
|
+
search: parsedUrl.search
|
|
8213
|
+
};
|
|
8214
|
+
} catch {
|
|
8215
|
+
return null;
|
|
8216
|
+
}
|
|
8217
|
+
}
|
|
8218
|
+
updateConfig(config) {
|
|
8219
|
+
this.setAllowedProtocols([
|
|
8220
|
+
"http:",
|
|
8221
|
+
"https:",
|
|
8222
|
+
"mailto:",
|
|
8223
|
+
"sms:",
|
|
8224
|
+
"tel:",
|
|
8225
|
+
...config?.schemaLinkRenderers?.map(({ protocol }) => protocol) || [],
|
|
8226
|
+
...config?.allowedProtocols || []
|
|
8227
|
+
]);
|
|
8228
|
+
this.setEmbedRules(config?.linkEmbedRules);
|
|
8229
|
+
this.setLabels(config?.labels);
|
|
8230
|
+
this.setSchemaLinkRenderers(config?.schemaLinkRenderers);
|
|
8231
|
+
this.setSchemaRules([...config?.schemaRules || [], ...createLegacySchemaRules(config?.schemaLinkRenderers)]);
|
|
8232
|
+
this.setToolbarActions(config?.toolbarActions);
|
|
8233
|
+
}
|
|
8234
|
+
setAllowedProtocols(protocols) {
|
|
8235
|
+
this.protocolPolicy.setAllowedProtocols(protocols);
|
|
8236
|
+
}
|
|
8237
|
+
setEmbedRules(rules = []) {
|
|
8238
|
+
this.ruleRegistry.setEmbedRules(rules);
|
|
8239
|
+
}
|
|
8240
|
+
setLabels(labels) {
|
|
8241
|
+
this.toolbarController.setLabels(labels);
|
|
8242
|
+
}
|
|
8243
|
+
setSchemaLinkRenderers(renderers = []) {
|
|
8244
|
+
this.legacySchemaLinkRegistry.setSchemaLinkRenderers(renderers);
|
|
8245
|
+
}
|
|
8246
|
+
setSchemaRules(rules = []) {
|
|
8247
|
+
this.ruleRegistry.setSchemaRules(rules);
|
|
8248
|
+
}
|
|
8249
|
+
setToolbarActions(actions = []) {
|
|
8250
|
+
this.toolbarController.setToolbarActions(actions);
|
|
8251
|
+
this.emit("change");
|
|
8252
|
+
}
|
|
8253
|
+
getToolbarItems(context) {
|
|
8254
|
+
return Array.from(this.toolbarItems.values()).filter((item) => item.when ? item.when(context) : true).sort((a, b) => (a.order || 0) - (b.order || 0));
|
|
8255
|
+
}
|
|
8256
|
+
registerToolbarItem(item) {
|
|
8257
|
+
this.toolbarItems.set(item.key, item);
|
|
8258
|
+
this.emit("change");
|
|
8259
|
+
return () => {
|
|
8260
|
+
this.toolbarItems.delete(item.key);
|
|
8261
|
+
this.emit("change");
|
|
8262
|
+
};
|
|
8263
|
+
}
|
|
8264
|
+
setLinkToolbar(enable) {
|
|
8265
|
+
if (this.toolbarController.setEnabled(enable)) this.emit("linkToolbarChange", enable);
|
|
8266
|
+
}
|
|
8267
|
+
suppressLinkToolbar(reason = "unknown") {
|
|
8268
|
+
const { changed, token } = this.toolbarController.suppress(reason);
|
|
8269
|
+
if (changed) this.emit("linkToolbarChange", this.toolbarController.enabled);
|
|
8270
|
+
return token;
|
|
8271
|
+
}
|
|
8272
|
+
restoreLinkToolbar(token) {
|
|
8273
|
+
if (this.toolbarController.restore(token)) this.emit("linkToolbarChange", this.toolbarController.enabled);
|
|
8274
|
+
}
|
|
8275
|
+
subscribe(listener) {
|
|
8276
|
+
this.on("change", listener);
|
|
8277
|
+
return () => {
|
|
8278
|
+
this.off("change", listener);
|
|
8279
|
+
};
|
|
8280
|
+
}
|
|
8281
|
+
subscribeCardMetadata(listener) {
|
|
8282
|
+
this.on("cardMetadataChange", listener);
|
|
8283
|
+
return () => {
|
|
8284
|
+
this.off("cardMetadataChange", listener);
|
|
8285
|
+
};
|
|
8286
|
+
}
|
|
8287
|
+
};
|
|
8288
|
+
function normalizeProtocol(protocol) {
|
|
8289
|
+
return `${protocol.split(":")[0]}:`;
|
|
8290
|
+
}
|
|
8291
|
+
function createLegacySchemaRules(renderers = []) {
|
|
8292
|
+
return renderers.map(({ protocol }) => {
|
|
8293
|
+
const normalizedProtocol = normalizeProtocol(protocol);
|
|
8294
|
+
return {
|
|
8295
|
+
id: normalizedProtocol.replace(":", ""),
|
|
8296
|
+
match: (url) => {
|
|
8297
|
+
try {
|
|
8298
|
+
return new URL(url).protocol === normalizedProtocol;
|
|
8299
|
+
} catch {
|
|
8300
|
+
return false;
|
|
8301
|
+
}
|
|
8302
|
+
}
|
|
8303
|
+
};
|
|
8304
|
+
});
|
|
8305
|
+
}
|
|
8306
|
+
//#endregion
|
|
8307
|
+
//#region src/plugins/link/node/LinkNode.ts
|
|
8308
|
+
const logger = createDebugLogger("plugin", "link");
|
|
8309
|
+
const SUPPORTED_URL_PROTOCOLS$1 = /* @__PURE__ */ new Set([
|
|
8310
|
+
"http:",
|
|
8311
|
+
"https:",
|
|
8312
|
+
"mailto:",
|
|
8313
|
+
"sms:",
|
|
8314
|
+
"tel:"
|
|
8315
|
+
]);
|
|
8316
|
+
const HOVER_LINK_COMMAND = createCommand("HOVER_LINK_COMMAND");
|
|
8317
|
+
const HOVER_OUT_LINK_COMMAND = createCommand("HOVER_OUT_LINK_COMMAND");
|
|
8318
|
+
/** @noInheritDoc */
|
|
8319
|
+
var LinkNode = class LinkNode extends ElementNode {
|
|
8320
|
+
static getType() {
|
|
8321
|
+
return "link";
|
|
8322
|
+
}
|
|
8323
|
+
static clone(node) {
|
|
8324
|
+
return new LinkNode(node.__url, {
|
|
8325
|
+
rel: node.__rel,
|
|
8326
|
+
target: node.__target,
|
|
8327
|
+
title: node.__title
|
|
8328
|
+
}, node.__key);
|
|
8329
|
+
}
|
|
8330
|
+
constructor(url = "", attributes = {}, key) {
|
|
8331
|
+
super(key);
|
|
8332
|
+
const { target = null, rel = null, title = null } = attributes;
|
|
8333
|
+
this.__url = url;
|
|
8334
|
+
this.__target = target;
|
|
8335
|
+
this.__rel = rel;
|
|
8336
|
+
this.__title = title;
|
|
8337
|
+
}
|
|
8338
|
+
createDOM(config, editor) {
|
|
8339
|
+
logger.debug("🔍 config", config);
|
|
8340
|
+
const element = document.createElement("a");
|
|
8341
|
+
this.updateLinkDOM(null, element, config);
|
|
8342
|
+
addClassNamesToElement(element, config.theme.link);
|
|
8343
|
+
element.addEventListener("mouseenter", (event) => {
|
|
8344
|
+
if (event.target instanceof HTMLElement) {
|
|
8345
|
+
event.target.classList.add("hover");
|
|
8346
|
+
editor.dispatchCommand(HOVER_LINK_COMMAND, {
|
|
8347
|
+
event,
|
|
8348
|
+
node: this
|
|
8349
|
+
});
|
|
8350
|
+
}
|
|
8351
|
+
});
|
|
8352
|
+
element.addEventListener("mouseleave", (event) => {
|
|
8353
|
+
if (event.target instanceof HTMLElement) {
|
|
8354
|
+
event.target.classList.remove("hover");
|
|
8355
|
+
editor.dispatchCommand(HOVER_OUT_LINK_COMMAND, { event });
|
|
8356
|
+
}
|
|
8357
|
+
});
|
|
8358
|
+
return element;
|
|
8359
|
+
}
|
|
8360
|
+
updateLinkDOM(prevNode, anchor, _config) {
|
|
8361
|
+
if (isHTMLAnchorElement(anchor)) {
|
|
8362
|
+
const linkService = getLinkServiceFromConfig(_config);
|
|
8363
|
+
if (!prevNode || prevNode.__url !== this.__url) anchor.href = this.sanitizeUrl(this.__url, linkService?.getAllowedProtocols());
|
|
6821
8364
|
for (const attr of [
|
|
6822
8365
|
"target",
|
|
6823
8366
|
"rel",
|
|
@@ -6848,11 +8391,11 @@ var LinkNode = class LinkNode extends ElementNode {
|
|
|
6848
8391
|
updateFromJSON(serializedNode) {
|
|
6849
8392
|
return super.updateFromJSON(serializedNode).setURL(serializedNode.url).setRel(serializedNode.rel || null).setTarget(serializedNode.target || null).setTitle(serializedNode.title || null);
|
|
6850
8393
|
}
|
|
6851
|
-
sanitizeUrl(url) {
|
|
8394
|
+
sanitizeUrl(url, allowedProtocols = SUPPORTED_URL_PROTOCOLS$1) {
|
|
6852
8395
|
url = formatUrl(url);
|
|
6853
8396
|
try {
|
|
6854
8397
|
const parsedUrl = new URL(formatUrl(url));
|
|
6855
|
-
if (!
|
|
8398
|
+
if (!allowedProtocols.has(parsedUrl.protocol)) return "about:blank";
|
|
6856
8399
|
} catch {
|
|
6857
8400
|
return url;
|
|
6858
8401
|
}
|
|
@@ -6932,7 +8475,13 @@ var LinkNode = class LinkNode extends ElementNode {
|
|
|
6932
8475
|
isWebSiteURI() {
|
|
6933
8476
|
return this.__url.startsWith("https://") || this.__url.startsWith("http://");
|
|
6934
8477
|
}
|
|
8478
|
+
getDOMSlot(element) {
|
|
8479
|
+
return super.getDOMSlot(element);
|
|
8480
|
+
}
|
|
6935
8481
|
};
|
|
8482
|
+
function getLinkServiceFromConfig(config) {
|
|
8483
|
+
return getKernelFromEditorConfig(config)?.requireService(ILinkService) || null;
|
|
8484
|
+
}
|
|
6936
8485
|
function $convertAnchorElement(domNode) {
|
|
6937
8486
|
let node = null;
|
|
6938
8487
|
if (isHTMLAnchorElement(domNode)) {
|
|
@@ -6960,7 +8509,7 @@ function $createLinkNode(url = "", attributes) {
|
|
|
6960
8509
|
* @returns true if node is a LinkNode, false otherwise.
|
|
6961
8510
|
*/
|
|
6962
8511
|
function $isLinkNode(node) {
|
|
6963
|
-
return node instanceof LinkNode;
|
|
8512
|
+
return node instanceof LinkNode || node?.getType?.() === "link" || node?.getType?.() === "autolink";
|
|
6964
8513
|
}
|
|
6965
8514
|
var AutoLinkNode = class AutoLinkNode extends LinkNode {
|
|
6966
8515
|
constructor(url = "", attributes = {}, key) {
|
|
@@ -7208,6 +8757,7 @@ function formatUrl(url) {
|
|
|
7208
8757
|
//#region src/plugins/link/command/index.ts
|
|
7209
8758
|
const INSERT_LINK_COMMAND = createCommand("INSERT_LINK_COMMAND");
|
|
7210
8759
|
const UPDATE_LINK_TEXT_COMMAND = createCommand("UPDATE_LINK_TEXT_COMMAND");
|
|
8760
|
+
const UNLINK_LINK_COMMAND = createCommand("UNLINK_LINK_COMMAND");
|
|
7211
8761
|
function registerLinkCommand(editor) {
|
|
7212
8762
|
return mergeRegister(editor.registerCommand(INSERT_LINK_COMMAND, (payload) => {
|
|
7213
8763
|
const { url, title = url } = payload;
|
|
@@ -7217,38 +8767,564 @@ function registerLinkCommand(editor) {
|
|
|
7217
8767
|
linkNode.append(textNode);
|
|
7218
8768
|
$insertNodes([linkNode]);
|
|
7219
8769
|
});
|
|
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);
|
|
8770
|
+
return false;
|
|
8771
|
+
}, COMMAND_PRIORITY_EDITOR), editor.registerCommand(UPDATE_LINK_TEXT_COMMAND, (payload) => {
|
|
8772
|
+
const { key, text } = payload;
|
|
8773
|
+
editor.update(() => {
|
|
8774
|
+
const linkNode = $getNodeByKey(key);
|
|
8775
|
+
if (linkNode) {
|
|
8776
|
+
const newLinkNode = $createLinkNode(linkNode.getURL(), { title: text });
|
|
8777
|
+
const textNode = $createTextNode(text);
|
|
8778
|
+
newLinkNode.append(textNode);
|
|
8779
|
+
linkNode?.replace(newLinkNode);
|
|
8780
|
+
newLinkNode.select(1);
|
|
8781
|
+
}
|
|
8782
|
+
});
|
|
8783
|
+
return false;
|
|
8784
|
+
}, COMMAND_PRIORITY_EDITOR), editor.registerCommand(UNLINK_LINK_COMMAND, ({ key }) => {
|
|
8785
|
+
editor.update(() => {
|
|
8786
|
+
const node = $getNodeByKey(key);
|
|
8787
|
+
if (!$isLinkNode(node)) return;
|
|
8788
|
+
let selection = $getSelection();
|
|
8789
|
+
if (!selection || !$isRangeSelection(selection)) {
|
|
8790
|
+
$setSelection($createRangeSelection());
|
|
8791
|
+
selection = $getSelection();
|
|
8792
|
+
}
|
|
8793
|
+
const first = node.getFirstDescendant();
|
|
8794
|
+
const last = node.getLastDescendant();
|
|
8795
|
+
if (selection && $isRangeSelection(selection) && $isTextNode(first) && $isTextNode(last)) {
|
|
8796
|
+
selection.anchor.set(first.getKey(), 0, "text");
|
|
8797
|
+
selection.focus.set(last.getKey(), last.getTextContentSize(), "text");
|
|
8798
|
+
editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
|
|
8799
|
+
return;
|
|
8800
|
+
}
|
|
8801
|
+
const children = node.getChildren();
|
|
8802
|
+
for (const child of children) node.insertBefore(child);
|
|
8803
|
+
node.remove();
|
|
8804
|
+
});
|
|
8805
|
+
return true;
|
|
8806
|
+
}, COMMAND_PRIORITY_EDITOR));
|
|
8807
|
+
}
|
|
8808
|
+
createCommand();
|
|
8809
|
+
var LinkCardNode = class LinkCardNode extends DecoratorNode {
|
|
8810
|
+
static getType() {
|
|
8811
|
+
return "link-card";
|
|
8812
|
+
}
|
|
8813
|
+
static clone(node) {
|
|
8814
|
+
return new LinkCardNode(node.__url, node.__title, node.__icon, node.__description, node.__openTarget, node.__key);
|
|
8815
|
+
}
|
|
8816
|
+
static importJSON(serializedNode) {
|
|
8817
|
+
return $createLinkCardNode({
|
|
8818
|
+
description: serializedNode.description,
|
|
8819
|
+
icon: serializedNode.icon,
|
|
8820
|
+
openTarget: serializedNode.openTarget,
|
|
8821
|
+
title: serializedNode.title,
|
|
8822
|
+
url: serializedNode.url
|
|
8823
|
+
}).updateFromJSON(serializedNode);
|
|
8824
|
+
}
|
|
8825
|
+
static importDOM() {
|
|
8826
|
+
return { a: (node) => {
|
|
8827
|
+
if (node instanceof HTMLAnchorElement && node.dataset.linkCard === "true" && node.dataset.linkCardLayout !== "block") return {
|
|
8828
|
+
conversion: $convertLinkCardElement,
|
|
8829
|
+
priority: 2
|
|
8830
|
+
};
|
|
8831
|
+
return null;
|
|
8832
|
+
} };
|
|
8833
|
+
}
|
|
8834
|
+
constructor(url, title, icon, description, openTarget, key) {
|
|
8835
|
+
super(key);
|
|
8836
|
+
this.__url = url;
|
|
8837
|
+
this.__title = title || url;
|
|
8838
|
+
this.__icon = icon || "";
|
|
8839
|
+
this.__description = description || "";
|
|
8840
|
+
this.__openTarget = openTarget ?? "_blank";
|
|
8841
|
+
}
|
|
8842
|
+
createDOM(config, editor) {
|
|
8843
|
+
const element = document.createElement("span");
|
|
8844
|
+
element.dataset.linkCard = "true";
|
|
8845
|
+
addClassNamesToElement(element, config.theme.linkCard);
|
|
8846
|
+
element.addEventListener("mouseenter", (event) => {
|
|
8847
|
+
if (event.target instanceof HTMLElement) {
|
|
8848
|
+
event.target.classList.add("hover");
|
|
8849
|
+
editor.dispatchCommand(HOVER_LINK_COMMAND, {
|
|
8850
|
+
event,
|
|
8851
|
+
node: this
|
|
8852
|
+
});
|
|
8853
|
+
}
|
|
8854
|
+
});
|
|
8855
|
+
element.addEventListener("mouseleave", (event) => {
|
|
8856
|
+
if (event.target instanceof HTMLElement) {
|
|
8857
|
+
event.target.classList.remove("hover");
|
|
8858
|
+
editor.dispatchCommand(HOVER_OUT_LINK_COMMAND, { event });
|
|
8859
|
+
}
|
|
8860
|
+
});
|
|
8861
|
+
return element;
|
|
8862
|
+
}
|
|
8863
|
+
updateDOM() {
|
|
8864
|
+
return false;
|
|
8865
|
+
}
|
|
8866
|
+
isInline() {
|
|
8867
|
+
return true;
|
|
8868
|
+
}
|
|
8869
|
+
isKeyboardSelectable() {
|
|
8870
|
+
return true;
|
|
8871
|
+
}
|
|
8872
|
+
exportDOM() {
|
|
8873
|
+
const element = document.createElement("a");
|
|
8874
|
+
element.dataset.linkCard = "true";
|
|
8875
|
+
element.dataset.icon = this.getIcon();
|
|
8876
|
+
element.dataset.description = this.getDescription();
|
|
8877
|
+
if (this.getOpenTarget()) element.target = this.getOpenTarget();
|
|
8878
|
+
element.href = this.getURL();
|
|
8879
|
+
element.textContent = this.getTitle();
|
|
8880
|
+
return { element };
|
|
8881
|
+
}
|
|
8882
|
+
exportJSON() {
|
|
8883
|
+
return {
|
|
8884
|
+
...super.exportJSON(),
|
|
8885
|
+
description: this.getDescription(),
|
|
8886
|
+
icon: this.getIcon(),
|
|
8887
|
+
openTarget: this.getOpenTarget(),
|
|
8888
|
+
title: this.getTitle(),
|
|
8889
|
+
url: this.getURL()
|
|
8890
|
+
};
|
|
8891
|
+
}
|
|
8892
|
+
updateFromJSON(serializedNode) {
|
|
8893
|
+
return super.updateFromJSON(serializedNode).setURL(serializedNode.url).setTitle(serializedNode.title).setIcon(serializedNode.icon).setDescription(serializedNode.description).setOpenTarget(serializedNode.openTarget ?? "_blank");
|
|
8894
|
+
}
|
|
8895
|
+
getTextContent() {
|
|
8896
|
+
return this.getTitle();
|
|
8897
|
+
}
|
|
8898
|
+
getURL() {
|
|
8899
|
+
return this.getLatest().__url;
|
|
8900
|
+
}
|
|
8901
|
+
setURL(url) {
|
|
8902
|
+
const writable = this.getWritable();
|
|
8903
|
+
writable.__url = url;
|
|
8904
|
+
return writable;
|
|
8905
|
+
}
|
|
8906
|
+
getTitle() {
|
|
8907
|
+
return this.getLatest().__title || this.getURL();
|
|
8908
|
+
}
|
|
8909
|
+
setTitle(title) {
|
|
8910
|
+
const writable = this.getWritable();
|
|
8911
|
+
writable.__title = title || this.getURL();
|
|
8912
|
+
return writable;
|
|
8913
|
+
}
|
|
8914
|
+
getIcon() {
|
|
8915
|
+
return this.getLatest().__icon;
|
|
8916
|
+
}
|
|
8917
|
+
setIcon(icon) {
|
|
8918
|
+
const writable = this.getWritable();
|
|
8919
|
+
writable.__icon = icon || "";
|
|
8920
|
+
return writable;
|
|
8921
|
+
}
|
|
8922
|
+
getDescription() {
|
|
8923
|
+
return this.getLatest().__description;
|
|
8924
|
+
}
|
|
8925
|
+
setDescription(description) {
|
|
8926
|
+
const writable = this.getWritable();
|
|
8927
|
+
writable.__description = description || "";
|
|
8928
|
+
return writable;
|
|
8929
|
+
}
|
|
8930
|
+
getOpenTarget() {
|
|
8931
|
+
return this.getLatest().__openTarget;
|
|
8932
|
+
}
|
|
8933
|
+
setOpenTarget(openTarget) {
|
|
8934
|
+
const writable = this.getWritable();
|
|
8935
|
+
writable.__openTarget = openTarget ?? "_blank";
|
|
8936
|
+
return writable;
|
|
8937
|
+
}
|
|
8938
|
+
decorate(editor) {
|
|
8939
|
+
const decorator = getKernelFromEditor(editor)?.getDecorator(LinkCardNode.getType());
|
|
8940
|
+
if (!decorator) return null;
|
|
8941
|
+
if (typeof decorator === "function") return decorator(this, editor);
|
|
8942
|
+
return decorator.render(this, editor);
|
|
8943
|
+
}
|
|
8944
|
+
};
|
|
8945
|
+
function $createLinkCardNode(payload) {
|
|
8946
|
+
return $applyNodeReplacement(new LinkCardNode(payload.url, payload.title, payload.icon, payload.description, payload.openTarget));
|
|
8947
|
+
}
|
|
8948
|
+
function $convertLinkCardElement(domNode) {
|
|
8949
|
+
const element = domNode;
|
|
8950
|
+
return { node: $createLinkCardNode({
|
|
8951
|
+
description: element.dataset.description,
|
|
8952
|
+
icon: element.dataset.icon,
|
|
8953
|
+
openTarget: element.getAttribute("target"),
|
|
8954
|
+
title: element.textContent || element.href,
|
|
8955
|
+
url: element.getAttribute("href") || ""
|
|
8956
|
+
}) };
|
|
8957
|
+
}
|
|
8958
|
+
function $isLinkCardNode(node) {
|
|
8959
|
+
return node instanceof LinkCardNode || node?.getType?.() === LinkCardNode.getType();
|
|
8960
|
+
}
|
|
8961
|
+
//#endregion
|
|
8962
|
+
//#region src/plugins/link/node/LinkBlockCardNode.ts
|
|
8963
|
+
var LinkBlockCardNode = class LinkBlockCardNode extends LinkCardNode {
|
|
8964
|
+
static getType() {
|
|
8965
|
+
return "link-block-card";
|
|
8966
|
+
}
|
|
8967
|
+
static clone(node) {
|
|
8968
|
+
return new LinkBlockCardNode(node.__url, node.__title, node.__icon, node.__description, node.__openTarget, node.__key);
|
|
8969
|
+
}
|
|
8970
|
+
static importJSON(serializedNode) {
|
|
8971
|
+
return $createLinkBlockCardNode({
|
|
8972
|
+
description: serializedNode.description,
|
|
8973
|
+
icon: serializedNode.icon,
|
|
8974
|
+
openTarget: serializedNode.openTarget,
|
|
8975
|
+
title: serializedNode.title,
|
|
8976
|
+
url: serializedNode.url
|
|
8977
|
+
}).updateFromJSON(serializedNode);
|
|
8978
|
+
}
|
|
8979
|
+
static importDOM() {
|
|
8980
|
+
return { a: (node) => {
|
|
8981
|
+
if (node instanceof HTMLAnchorElement && node.dataset.linkCard === "true" && node.dataset.linkCardLayout === "block") return {
|
|
8982
|
+
conversion: $convertLinkBlockCardElement,
|
|
8983
|
+
priority: 3
|
|
8984
|
+
};
|
|
8985
|
+
return null;
|
|
8986
|
+
} };
|
|
8987
|
+
}
|
|
8988
|
+
constructor(url, title, icon, description, openTarget, key) {
|
|
8989
|
+
super(url, title, icon, description, openTarget, key);
|
|
8990
|
+
}
|
|
8991
|
+
createDOM(config, editor) {
|
|
8992
|
+
const element = super.createDOM(config, editor);
|
|
8993
|
+
element.dataset.linkCardLayout = "block";
|
|
8994
|
+
return element;
|
|
8995
|
+
}
|
|
8996
|
+
exportDOM() {
|
|
8997
|
+
const output = super.exportDOM();
|
|
8998
|
+
if (output.element instanceof HTMLElement) output.element.dataset.linkCardLayout = "block";
|
|
8999
|
+
return output;
|
|
9000
|
+
}
|
|
9001
|
+
updateFromJSON(serializedNode) {
|
|
9002
|
+
return super.updateFromJSON(serializedNode);
|
|
9003
|
+
}
|
|
9004
|
+
isInline() {
|
|
9005
|
+
return false;
|
|
9006
|
+
}
|
|
9007
|
+
};
|
|
9008
|
+
function $createLinkBlockCardNode(payload) {
|
|
9009
|
+
return $applyNodeReplacement(new LinkBlockCardNode(payload.url, payload.title, payload.icon, payload.description, payload.openTarget));
|
|
9010
|
+
}
|
|
9011
|
+
function $convertLinkBlockCardElement(domNode) {
|
|
9012
|
+
const element = domNode;
|
|
9013
|
+
return { node: $createLinkBlockCardNode({
|
|
9014
|
+
description: element.dataset.description,
|
|
9015
|
+
icon: element.dataset.icon,
|
|
9016
|
+
openTarget: element.getAttribute("target"),
|
|
9017
|
+
title: element.textContent || element.href,
|
|
9018
|
+
url: element.getAttribute("href") || ""
|
|
9019
|
+
}) };
|
|
9020
|
+
}
|
|
9021
|
+
function $isLinkBlockCardNode(node) {
|
|
9022
|
+
return node instanceof LinkBlockCardNode || node?.getType?.() === LinkBlockCardNode.getType();
|
|
9023
|
+
}
|
|
9024
|
+
//#endregion
|
|
9025
|
+
//#region src/plugins/link/node/LinkIframeNode.ts
|
|
9026
|
+
var LinkIframeNode = class LinkIframeNode extends DecoratorNode {
|
|
9027
|
+
static getType() {
|
|
9028
|
+
return "link-iframe";
|
|
9029
|
+
}
|
|
9030
|
+
static clone(node) {
|
|
9031
|
+
return new LinkIframeNode(node.__url, node.__src, node.__title, node.__key);
|
|
9032
|
+
}
|
|
9033
|
+
static importJSON(serializedNode) {
|
|
9034
|
+
return $createLinkIframeNode({
|
|
9035
|
+
src: serializedNode.src,
|
|
9036
|
+
title: serializedNode.title,
|
|
9037
|
+
url: serializedNode.url
|
|
9038
|
+
}).updateFromJSON(serializedNode);
|
|
9039
|
+
}
|
|
9040
|
+
static importDOM() {
|
|
9041
|
+
return { a: (node) => {
|
|
9042
|
+
if (node instanceof HTMLAnchorElement && node.dataset.linkIframe === "true") return {
|
|
9043
|
+
conversion: $convertLinkIframeElement,
|
|
9044
|
+
priority: 2
|
|
9045
|
+
};
|
|
9046
|
+
return null;
|
|
9047
|
+
} };
|
|
9048
|
+
}
|
|
9049
|
+
constructor(url, src, title, key) {
|
|
9050
|
+
super(key);
|
|
9051
|
+
this.__url = url;
|
|
9052
|
+
this.__src = src || url;
|
|
9053
|
+
this.__title = title || url;
|
|
9054
|
+
}
|
|
9055
|
+
createDOM(config, editor) {
|
|
9056
|
+
const element = document.createElement("div");
|
|
9057
|
+
element.dataset.linkIframe = "true";
|
|
9058
|
+
addClassNamesToElement(element, config.theme.linkIframe);
|
|
9059
|
+
element.addEventListener("mouseenter", (event) => {
|
|
9060
|
+
if (event.target instanceof HTMLElement) {
|
|
9061
|
+
event.target.classList.add("hover");
|
|
9062
|
+
editor.dispatchCommand(HOVER_LINK_COMMAND, {
|
|
9063
|
+
event,
|
|
9064
|
+
node: this
|
|
9065
|
+
});
|
|
9066
|
+
}
|
|
9067
|
+
});
|
|
9068
|
+
element.addEventListener("mouseleave", (event) => {
|
|
9069
|
+
if (event.target instanceof HTMLElement) {
|
|
9070
|
+
event.target.classList.remove("hover");
|
|
9071
|
+
editor.dispatchCommand(HOVER_OUT_LINK_COMMAND, { event });
|
|
9072
|
+
}
|
|
9073
|
+
});
|
|
9074
|
+
return element;
|
|
9075
|
+
}
|
|
9076
|
+
updateDOM() {
|
|
9077
|
+
return false;
|
|
9078
|
+
}
|
|
9079
|
+
isInline() {
|
|
9080
|
+
return false;
|
|
9081
|
+
}
|
|
9082
|
+
exportDOM() {
|
|
9083
|
+
const element = document.createElement("a");
|
|
9084
|
+
element.dataset.linkIframe = "true";
|
|
9085
|
+
element.dataset.src = this.getSrc();
|
|
9086
|
+
element.href = this.getURL();
|
|
9087
|
+
element.textContent = this.getTitle();
|
|
9088
|
+
return { element };
|
|
9089
|
+
}
|
|
9090
|
+
exportJSON() {
|
|
9091
|
+
return {
|
|
9092
|
+
...super.exportJSON(),
|
|
9093
|
+
src: this.getSrc(),
|
|
9094
|
+
title: this.getTitle(),
|
|
9095
|
+
url: this.getURL()
|
|
9096
|
+
};
|
|
9097
|
+
}
|
|
9098
|
+
updateFromJSON(serializedNode) {
|
|
9099
|
+
return super.updateFromJSON(serializedNode).setURL(serializedNode.url).setSrc(serializedNode.src).setTitle(serializedNode.title);
|
|
9100
|
+
}
|
|
9101
|
+
getTextContent() {
|
|
9102
|
+
return this.getTitle();
|
|
9103
|
+
}
|
|
9104
|
+
getURL() {
|
|
9105
|
+
return this.getLatest().__url;
|
|
9106
|
+
}
|
|
9107
|
+
setURL(url) {
|
|
9108
|
+
const writable = this.getWritable();
|
|
9109
|
+
writable.__url = url;
|
|
9110
|
+
return writable;
|
|
9111
|
+
}
|
|
9112
|
+
getSrc() {
|
|
9113
|
+
return this.getLatest().__src || this.getURL();
|
|
9114
|
+
}
|
|
9115
|
+
setSrc(src) {
|
|
9116
|
+
const writable = this.getWritable();
|
|
9117
|
+
writable.__src = src || this.getURL();
|
|
9118
|
+
return writable;
|
|
9119
|
+
}
|
|
9120
|
+
getTitle() {
|
|
9121
|
+
return this.getLatest().__title || this.getURL();
|
|
9122
|
+
}
|
|
9123
|
+
setTitle(title) {
|
|
9124
|
+
const writable = this.getWritable();
|
|
9125
|
+
writable.__title = title || this.getURL();
|
|
9126
|
+
return writable;
|
|
9127
|
+
}
|
|
9128
|
+
decorate(editor) {
|
|
9129
|
+
const decorator = getKernelFromEditor(editor)?.getDecorator(LinkIframeNode.getType());
|
|
9130
|
+
if (!decorator) return null;
|
|
9131
|
+
if (typeof decorator === "function") return decorator(this, editor);
|
|
9132
|
+
return decorator.render(this, editor);
|
|
9133
|
+
}
|
|
9134
|
+
};
|
|
9135
|
+
function $createLinkIframeNode(payload) {
|
|
9136
|
+
return $applyNodeReplacement(new LinkIframeNode(payload.url, payload.src, payload.title));
|
|
9137
|
+
}
|
|
9138
|
+
function $convertLinkIframeElement(domNode) {
|
|
9139
|
+
const element = domNode;
|
|
9140
|
+
return { node: $createLinkIframeNode({
|
|
9141
|
+
src: element.dataset.src,
|
|
9142
|
+
title: element.textContent || element.href,
|
|
9143
|
+
url: element.getAttribute("href") || ""
|
|
9144
|
+
}) };
|
|
9145
|
+
}
|
|
9146
|
+
function $isLinkIframeNode(node) {
|
|
9147
|
+
return node instanceof LinkIframeNode || node?.getType?.() === LinkIframeNode.getType();
|
|
9148
|
+
}
|
|
9149
|
+
//#endregion
|
|
9150
|
+
//#region src/plugins/link/node/SchemaNode.ts
|
|
9151
|
+
var SchemaNode = class SchemaNode extends DecoratorNode {
|
|
9152
|
+
static getType() {
|
|
9153
|
+
return "schema-link";
|
|
9154
|
+
}
|
|
9155
|
+
static clone(node) {
|
|
9156
|
+
return new SchemaNode(node.__url, node.__schemaType, node.__payload, node.__title, node.__key);
|
|
9157
|
+
}
|
|
9158
|
+
static importJSON(serializedNode) {
|
|
9159
|
+
return $createSchemaNode({
|
|
9160
|
+
payload: serializedNode.payload,
|
|
9161
|
+
schemaType: serializedNode.schemaType,
|
|
9162
|
+
title: serializedNode.title,
|
|
9163
|
+
url: serializedNode.url
|
|
9164
|
+
}).updateFromJSON(serializedNode);
|
|
9165
|
+
}
|
|
9166
|
+
static importDOM() {
|
|
9167
|
+
return { a: (node) => {
|
|
9168
|
+
if (node instanceof HTMLAnchorElement && node.dataset.schemaLink === "true") return {
|
|
9169
|
+
conversion: $convertSchemaElement,
|
|
9170
|
+
priority: 2
|
|
9171
|
+
};
|
|
9172
|
+
return null;
|
|
9173
|
+
} };
|
|
9174
|
+
}
|
|
9175
|
+
constructor(url, schemaType, payload, title, key) {
|
|
9176
|
+
super(key);
|
|
9177
|
+
this.__url = url;
|
|
9178
|
+
this.__schemaType = schemaType || "";
|
|
9179
|
+
this.__payload = payload;
|
|
9180
|
+
this.__title = title || url;
|
|
9181
|
+
}
|
|
9182
|
+
createDOM(config, editor) {
|
|
9183
|
+
const element = document.createElement("div");
|
|
9184
|
+
element.dataset.schemaLink = "true";
|
|
9185
|
+
addClassNamesToElement(element, config.theme.schemaLink);
|
|
9186
|
+
element.addEventListener("mouseenter", (event) => {
|
|
9187
|
+
if (event.target instanceof HTMLElement) {
|
|
9188
|
+
event.target.classList.add("hover");
|
|
9189
|
+
editor.dispatchCommand(HOVER_LINK_COMMAND, {
|
|
9190
|
+
event,
|
|
9191
|
+
node: this
|
|
9192
|
+
});
|
|
9193
|
+
}
|
|
9194
|
+
});
|
|
9195
|
+
element.addEventListener("mouseleave", (event) => {
|
|
9196
|
+
if (event.target instanceof HTMLElement) {
|
|
9197
|
+
event.target.classList.remove("hover");
|
|
9198
|
+
editor.dispatchCommand(HOVER_OUT_LINK_COMMAND, { event });
|
|
7231
9199
|
}
|
|
7232
9200
|
});
|
|
9201
|
+
return element;
|
|
9202
|
+
}
|
|
9203
|
+
updateDOM() {
|
|
7233
9204
|
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
9205
|
}
|
|
7244
|
-
|
|
7245
|
-
|
|
9206
|
+
exportDOM() {
|
|
9207
|
+
const element = document.createElement("a");
|
|
9208
|
+
element.dataset.schemaLink = "true";
|
|
9209
|
+
element.dataset.schemaType = this.getSchemaType();
|
|
9210
|
+
element.dataset.payload = JSON.stringify(this.getPayload() ?? null);
|
|
9211
|
+
element.href = this.getURL();
|
|
9212
|
+
element.textContent = this.getTitle();
|
|
9213
|
+
return { element };
|
|
7246
9214
|
}
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
|
|
9215
|
+
exportJSON() {
|
|
9216
|
+
return {
|
|
9217
|
+
...super.exportJSON(),
|
|
9218
|
+
payload: this.getPayload(),
|
|
9219
|
+
schemaType: this.getSchemaType(),
|
|
9220
|
+
title: this.getTitle(),
|
|
9221
|
+
url: this.getURL()
|
|
9222
|
+
};
|
|
9223
|
+
}
|
|
9224
|
+
updateFromJSON(serializedNode) {
|
|
9225
|
+
return super.updateFromJSON(serializedNode).setURL(serializedNode.url).setSchemaType(serializedNode.schemaType).setPayload(serializedNode.payload).setTitle(serializedNode.title);
|
|
9226
|
+
}
|
|
9227
|
+
getTextContent() {
|
|
9228
|
+
return this.getTitle();
|
|
9229
|
+
}
|
|
9230
|
+
getURL() {
|
|
9231
|
+
return this.getLatest().__url;
|
|
9232
|
+
}
|
|
9233
|
+
setURL(url) {
|
|
9234
|
+
const writable = this.getWritable();
|
|
9235
|
+
writable.__url = url;
|
|
9236
|
+
return writable;
|
|
9237
|
+
}
|
|
9238
|
+
getSchemaType() {
|
|
9239
|
+
return this.getLatest().__schemaType;
|
|
9240
|
+
}
|
|
9241
|
+
setSchemaType(schemaType) {
|
|
9242
|
+
const writable = this.getWritable();
|
|
9243
|
+
writable.__schemaType = schemaType || "";
|
|
9244
|
+
return writable;
|
|
9245
|
+
}
|
|
9246
|
+
getPayload() {
|
|
9247
|
+
return this.getLatest().__payload;
|
|
9248
|
+
}
|
|
9249
|
+
setPayload(payload) {
|
|
9250
|
+
const writable = this.getWritable();
|
|
9251
|
+
writable.__payload = payload;
|
|
9252
|
+
return writable;
|
|
9253
|
+
}
|
|
9254
|
+
getTitle() {
|
|
9255
|
+
return this.getLatest().__title || this.getURL();
|
|
9256
|
+
}
|
|
9257
|
+
setTitle(title) {
|
|
9258
|
+
const writable = this.getWritable();
|
|
9259
|
+
writable.__title = title || this.getURL();
|
|
9260
|
+
return writable;
|
|
9261
|
+
}
|
|
9262
|
+
decorate(editor) {
|
|
9263
|
+
const decorator = getKernelFromEditor(editor)?.getDecorator(SchemaNode.getType());
|
|
9264
|
+
if (!decorator) return null;
|
|
9265
|
+
if (typeof decorator === "function") return decorator(this, editor);
|
|
9266
|
+
return decorator.render(this, editor);
|
|
7250
9267
|
}
|
|
7251
9268
|
};
|
|
9269
|
+
function $createSchemaNode(payload) {
|
|
9270
|
+
return $applyNodeReplacement(new SchemaNode(payload.url, payload.schemaType, payload.payload, payload.title));
|
|
9271
|
+
}
|
|
9272
|
+
function $convertSchemaElement(domNode) {
|
|
9273
|
+
const element = domNode;
|
|
9274
|
+
return { node: $createSchemaNode({
|
|
9275
|
+
payload: parsePayload(element.dataset.payload),
|
|
9276
|
+
schemaType: element.dataset.schemaType,
|
|
9277
|
+
title: element.textContent || element.href,
|
|
9278
|
+
url: element.getAttribute("href") || ""
|
|
9279
|
+
}) };
|
|
9280
|
+
}
|
|
9281
|
+
function parsePayload(value) {
|
|
9282
|
+
if (!value) return void 0;
|
|
9283
|
+
try {
|
|
9284
|
+
return JSON.parse(value);
|
|
9285
|
+
} catch {
|
|
9286
|
+
return value;
|
|
9287
|
+
}
|
|
9288
|
+
}
|
|
9289
|
+
function $isSchemaNode(node) {
|
|
9290
|
+
return node instanceof SchemaNode || node?.getType?.() === SchemaNode.getType();
|
|
9291
|
+
}
|
|
9292
|
+
//#endregion
|
|
9293
|
+
//#region src/plugins/link/normalization/index.ts
|
|
9294
|
+
function normalizeSchemaLinkNode(node, editor, linkService) {
|
|
9295
|
+
const url = node.getURL();
|
|
9296
|
+
const title = node.getTitle() || node.getTextContent() || url;
|
|
9297
|
+
const schema = linkService.parseSchemaUrl(url);
|
|
9298
|
+
const rule = linkService.getSchemaRule(url, {
|
|
9299
|
+
editor,
|
|
9300
|
+
schema,
|
|
9301
|
+
text: node.getTextContent(),
|
|
9302
|
+
title
|
|
9303
|
+
});
|
|
9304
|
+
if (!rule) return false;
|
|
9305
|
+
const parsed = rule.parse?.(url, schema);
|
|
9306
|
+
const payload = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? normalizeSchemaPayload(parsed) : { payload: parsed };
|
|
9307
|
+
if (linkService.hasSchemaLinkRenderer(url)) payload.payload = {
|
|
9308
|
+
...payload.payload && typeof payload.payload === "object" ? payload.payload : {},
|
|
9309
|
+
__legacyLink: {
|
|
9310
|
+
rel: node.getRel(),
|
|
9311
|
+
target: node.getTarget(),
|
|
9312
|
+
text: node.getTextContent(),
|
|
9313
|
+
title: node.getTitle()
|
|
9314
|
+
}
|
|
9315
|
+
};
|
|
9316
|
+
node.replace($createSchemaNode({
|
|
9317
|
+
payload: payload.payload,
|
|
9318
|
+
schemaType: payload.schemaType || rule.id,
|
|
9319
|
+
title: payload.title || title,
|
|
9320
|
+
url: payload.url || url
|
|
9321
|
+
}));
|
|
9322
|
+
return true;
|
|
9323
|
+
}
|
|
9324
|
+
function normalizeSchemaPayload(payload) {
|
|
9325
|
+
if ("payload" in payload || "schemaType" in payload || "title" in payload || "url" in payload) return payload;
|
|
9326
|
+
return { payload };
|
|
9327
|
+
}
|
|
7252
9328
|
//#endregion
|
|
7253
9329
|
//#region src/plugins/link/utils/index.ts
|
|
7254
9330
|
const SUPPORTED_URL_PROTOCOLS = /* @__PURE__ */ new Set([
|
|
@@ -7377,10 +9453,22 @@ const LinkPlugin = class extends KernelPlugin {
|
|
|
7377
9453
|
super();
|
|
7378
9454
|
this.kernel = kernel;
|
|
7379
9455
|
this.config = config;
|
|
7380
|
-
this.linkRegex = /^
|
|
9456
|
+
this.linkRegex = /^[a-z][\d+.a-z-]*:\/\/\S+$/i;
|
|
7381
9457
|
this.service = new LinkService();
|
|
7382
|
-
kernel.registerNodes([
|
|
9458
|
+
kernel.registerNodes([
|
|
9459
|
+
LinkNode,
|
|
9460
|
+
AutoLinkNode,
|
|
9461
|
+
LinkCardNode,
|
|
9462
|
+
LinkBlockCardNode,
|
|
9463
|
+
LinkIframeNode,
|
|
9464
|
+
SchemaNode
|
|
9465
|
+
]);
|
|
9466
|
+
this.service.updateConfig(config);
|
|
7383
9467
|
kernel.registerService(ILinkService, this.service);
|
|
9468
|
+
this.registerDecorator(kernel, LinkCardNode.getType(), (node, editor) => config?.decoratorCard ? config.decoratorCard(node, editor) : null);
|
|
9469
|
+
this.registerDecorator(kernel, LinkBlockCardNode.getType(), (node, editor) => config?.decoratorCard ? config.decoratorCard(node, editor) : null);
|
|
9470
|
+
this.registerDecorator(kernel, LinkIframeNode.getType(), (node, editor) => config?.decoratorIframe ? config.decoratorIframe(node, editor) : null);
|
|
9471
|
+
this.registerDecorator(kernel, SchemaNode.getType(), (node, editor) => config?.decoratorSchema ? config.decoratorSchema(node, editor) : null);
|
|
7384
9472
|
if (config?.theme) kernel.registerThemes(config.theme);
|
|
7385
9473
|
if (config?.linkRegex) this.linkRegex = config.linkRegex;
|
|
7386
9474
|
}
|
|
@@ -7404,6 +9492,9 @@ const LinkPlugin = class extends KernelPlugin {
|
|
|
7404
9492
|
}
|
|
7405
9493
|
return false;
|
|
7406
9494
|
}, COMMAND_PRIORITY_NORMAL));
|
|
9495
|
+
if (this.config?.normalizeSchemaLinks !== false) this.register(editor.registerNodeTransform(LinkNode, (node) => {
|
|
9496
|
+
normalizeSchemaLinkNode(node, editor, this.service);
|
|
9497
|
+
}));
|
|
7407
9498
|
this.registerMarkdown();
|
|
7408
9499
|
this.registerLiteXml();
|
|
7409
9500
|
}
|
|
@@ -7428,6 +9519,74 @@ const LinkPlugin = class extends KernelPlugin {
|
|
|
7428
9519
|
version: 1
|
|
7429
9520
|
})];
|
|
7430
9521
|
});
|
|
9522
|
+
litexmlService.registerXMLWriter(LinkCardNode.getType(), (node, ctx) => {
|
|
9523
|
+
if (!$isLinkCardNode(node)) return false;
|
|
9524
|
+
return ctx.createXmlNode("link-card", {
|
|
9525
|
+
description: node.getDescription(),
|
|
9526
|
+
href: node.getURL(),
|
|
9527
|
+
icon: node.getIcon(),
|
|
9528
|
+
openTarget: node.getOpenTarget() || "_blank",
|
|
9529
|
+
title: node.getTitle()
|
|
9530
|
+
});
|
|
9531
|
+
});
|
|
9532
|
+
litexmlService.registerXMLWriter(LinkBlockCardNode.getType(), (node, ctx) => {
|
|
9533
|
+
if (!$isLinkBlockCardNode(node)) return false;
|
|
9534
|
+
return ctx.createXmlNode("link-card", {
|
|
9535
|
+
description: node.getDescription(),
|
|
9536
|
+
href: node.getURL(),
|
|
9537
|
+
icon: node.getIcon(),
|
|
9538
|
+
layout: "block",
|
|
9539
|
+
openTarget: node.getOpenTarget() || "_blank",
|
|
9540
|
+
title: node.getTitle()
|
|
9541
|
+
});
|
|
9542
|
+
});
|
|
9543
|
+
litexmlService.registerXMLReader("link-card", (xmlNode) => {
|
|
9544
|
+
return [INodeHelper.createTypeNode(xmlNode.getAttribute("layout") === "block" ? LinkBlockCardNode.getType() : LinkCardNode.getType(), {
|
|
9545
|
+
description: xmlNode.getAttribute("description") || "",
|
|
9546
|
+
icon: xmlNode.getAttribute("icon") || "",
|
|
9547
|
+
openTarget: xmlNode.getAttribute("openTarget") || "_blank",
|
|
9548
|
+
title: xmlNode.getAttribute("title") || xmlNode.getAttribute("href") || "",
|
|
9549
|
+
type: xmlNode.getAttribute("layout") === "block" ? LinkBlockCardNode.getType() : LinkCardNode.getType(),
|
|
9550
|
+
url: xmlNode.getAttribute("href") || "",
|
|
9551
|
+
version: 1
|
|
9552
|
+
})];
|
|
9553
|
+
});
|
|
9554
|
+
litexmlService.registerXMLWriter(LinkIframeNode.getType(), (node, ctx) => {
|
|
9555
|
+
if (!$isLinkIframeNode(node)) return false;
|
|
9556
|
+
return ctx.createXmlNode("link-iframe", {
|
|
9557
|
+
href: node.getURL(),
|
|
9558
|
+
src: node.getSrc(),
|
|
9559
|
+
title: node.getTitle()
|
|
9560
|
+
});
|
|
9561
|
+
});
|
|
9562
|
+
litexmlService.registerXMLReader("link-iframe", (xmlNode) => {
|
|
9563
|
+
return [INodeHelper.createTypeNode(LinkIframeNode.getType(), {
|
|
9564
|
+
src: xmlNode.getAttribute("src") || xmlNode.getAttribute("href") || "",
|
|
9565
|
+
title: xmlNode.getAttribute("title") || xmlNode.getAttribute("href") || "",
|
|
9566
|
+
type: LinkIframeNode.getType(),
|
|
9567
|
+
url: xmlNode.getAttribute("href") || "",
|
|
9568
|
+
version: 1
|
|
9569
|
+
})];
|
|
9570
|
+
});
|
|
9571
|
+
litexmlService.registerXMLWriter(SchemaNode.getType(), (node, ctx) => {
|
|
9572
|
+
if (!$isSchemaNode(node)) return false;
|
|
9573
|
+
return ctx.createXmlNode("schema-link", {
|
|
9574
|
+
href: node.getURL(),
|
|
9575
|
+
payload: JSON.stringify(node.getPayload() ?? null),
|
|
9576
|
+
schemaType: node.getSchemaType(),
|
|
9577
|
+
title: node.getTitle()
|
|
9578
|
+
});
|
|
9579
|
+
});
|
|
9580
|
+
litexmlService.registerXMLReader("schema-link", (xmlNode) => {
|
|
9581
|
+
return [INodeHelper.createTypeNode(SchemaNode.getType(), {
|
|
9582
|
+
payload: parseJsonAttribute(xmlNode.getAttribute("payload")),
|
|
9583
|
+
schemaType: xmlNode.getAttribute("schemaType") || "",
|
|
9584
|
+
title: xmlNode.getAttribute("title") || xmlNode.getAttribute("href") || "",
|
|
9585
|
+
type: SchemaNode.getType(),
|
|
9586
|
+
url: xmlNode.getAttribute("href") || "",
|
|
9587
|
+
version: 1
|
|
9588
|
+
})];
|
|
9589
|
+
});
|
|
7431
9590
|
}
|
|
7432
9591
|
registerMarkdown() {
|
|
7433
9592
|
this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownShortCut({
|
|
@@ -7447,6 +9606,30 @@ const LinkPlugin = class extends KernelPlugin {
|
|
|
7447
9606
|
this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownWriter(LinkNode.getType(), (ctx, node) => {
|
|
7448
9607
|
if ($isLinkNode(node)) ctx.wrap("[", `](${node.getURL()})`);
|
|
7449
9608
|
});
|
|
9609
|
+
this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownWriter(LinkCardNode.getType(), (ctx, node) => {
|
|
9610
|
+
if ($isLinkCardNode(node)) {
|
|
9611
|
+
ctx.appendLine(`[${node.getTitle()}](${node.getURL()})\n`);
|
|
9612
|
+
return true;
|
|
9613
|
+
}
|
|
9614
|
+
});
|
|
9615
|
+
this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownWriter(LinkBlockCardNode.getType(), (ctx, node) => {
|
|
9616
|
+
if ($isLinkBlockCardNode(node)) {
|
|
9617
|
+
ctx.appendLine(`[${node.getTitle()}](${node.getURL()})\n`);
|
|
9618
|
+
return true;
|
|
9619
|
+
}
|
|
9620
|
+
});
|
|
9621
|
+
this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownWriter(LinkIframeNode.getType(), (ctx, node) => {
|
|
9622
|
+
if ($isLinkIframeNode(node)) {
|
|
9623
|
+
ctx.appendLine(`[${node.getTitle()}](${node.getURL()})\n`);
|
|
9624
|
+
return true;
|
|
9625
|
+
}
|
|
9626
|
+
});
|
|
9627
|
+
this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownWriter(SchemaNode.getType(), (ctx, node) => {
|
|
9628
|
+
if ($isSchemaNode(node)) {
|
|
9629
|
+
ctx.appendLine(`[${node.getTitle()}](${node.getURL()})\n`);
|
|
9630
|
+
return true;
|
|
9631
|
+
}
|
|
9632
|
+
});
|
|
7450
9633
|
this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownReader("link", (node, children) => {
|
|
7451
9634
|
return [INodeHelper.createElementNode("link", {
|
|
7452
9635
|
children,
|
|
@@ -7461,6 +9644,14 @@ const LinkPlugin = class extends KernelPlugin {
|
|
|
7461
9644
|
});
|
|
7462
9645
|
}
|
|
7463
9646
|
};
|
|
9647
|
+
function parseJsonAttribute(value) {
|
|
9648
|
+
if (!value) return void 0;
|
|
9649
|
+
try {
|
|
9650
|
+
return JSON.parse(value);
|
|
9651
|
+
} catch {
|
|
9652
|
+
return value;
|
|
9653
|
+
}
|
|
9654
|
+
}
|
|
7464
9655
|
//#endregion
|
|
7465
9656
|
//#region src/utils/cx.ts
|
|
7466
9657
|
const cx = (...classNames) => classNames.filter(Boolean).join(" ");
|
|
@@ -7819,7 +10010,11 @@ const ListPlugin = class extends KernelPlugin {
|
|
|
7819
10010
|
type: "element"
|
|
7820
10011
|
});
|
|
7821
10012
|
markdownService.registerMarkdownWriter(ListNode.getType(), (ctx, node) => {
|
|
7822
|
-
if ($isListNode(node))
|
|
10013
|
+
if ($isListNode(node)) {
|
|
10014
|
+
const parent = node.getParent();
|
|
10015
|
+
const needsLeadingNewline = $isListItemNode(parent) && node.getPreviousSibling() !== null;
|
|
10016
|
+
ctx.wrap(needsLeadingNewline ? "\n" : "", "\n");
|
|
10017
|
+
}
|
|
7823
10018
|
});
|
|
7824
10019
|
const getLevel = (node) => {
|
|
7825
10020
|
if (!node) return 0;
|
|
@@ -7850,7 +10045,10 @@ const ListPlugin = class extends KernelPlugin {
|
|
|
7850
10045
|
let start = node.start || 1;
|
|
7851
10046
|
return INodeHelper.createElementNode("list", {
|
|
7852
10047
|
children: children.map((v) => {
|
|
7853
|
-
if (v.type === "listitem")
|
|
10048
|
+
if (v.type === "listitem") {
|
|
10049
|
+
v.value = start;
|
|
10050
|
+
if (v.children?.[0]?.type !== "list") start++;
|
|
10051
|
+
}
|
|
7854
10052
|
return v;
|
|
7855
10053
|
}),
|
|
7856
10054
|
direction: "ltr",
|
|
@@ -8922,10 +11120,88 @@ var TableControllerMenuService = class {
|
|
|
8922
11120
|
}
|
|
8923
11121
|
};
|
|
8924
11122
|
//#endregion
|
|
11123
|
+
//#region src/plugins/table/plugin/single-cell-paste.ts
|
|
11124
|
+
const LEXICAL_CLIPBOARD_TYPE = "application/x-lexical-editor";
|
|
11125
|
+
const countNodesByType = (node, type) => {
|
|
11126
|
+
return (node.type === type ? 1 : 0) + (Array.isArray(node.children) ? node.children.reduce((count, child) => count + countNodesByType(child, type), 0) : 0);
|
|
11127
|
+
};
|
|
11128
|
+
const parseLexicalTableClipboard = (serializedClipboard) => {
|
|
11129
|
+
if (!serializedClipboard) return void 0;
|
|
11130
|
+
try {
|
|
11131
|
+
const clipboard = JSON.parse(serializedClipboard);
|
|
11132
|
+
if (!Array.isArray(clipboard.nodes) || clipboard.nodes.length !== 1) return { isSingleCell: false };
|
|
11133
|
+
const [rootNode] = clipboard.nodes;
|
|
11134
|
+
if (rootNode?.type !== "table") return { isSingleCell: false };
|
|
11135
|
+
const cells = [];
|
|
11136
|
+
const collectCells = (node) => {
|
|
11137
|
+
if (node.type === "tablecell") cells.push(node);
|
|
11138
|
+
node.children?.forEach(collectCells);
|
|
11139
|
+
};
|
|
11140
|
+
collectCells(rootNode);
|
|
11141
|
+
const isSingleCell = countNodesByType(rootNode, "table") === 1 && cells.length === 1;
|
|
11142
|
+
return {
|
|
11143
|
+
cell: isSingleCell ? cells[0] : void 0,
|
|
11144
|
+
isSingleCell
|
|
11145
|
+
};
|
|
11146
|
+
} catch {
|
|
11147
|
+
return;
|
|
11148
|
+
}
|
|
11149
|
+
};
|
|
11150
|
+
const getSerializedNodeText = (node) => {
|
|
11151
|
+
if (node.type === "text") return node.text || "";
|
|
11152
|
+
if (node.type === "linebreak") return "\n";
|
|
11153
|
+
if (node.type === "tab") return " ";
|
|
11154
|
+
return node.children?.map(getSerializedNodeText).join("") || "";
|
|
11155
|
+
};
|
|
11156
|
+
const getSerializedCellText = (cell) => {
|
|
11157
|
+
return cell.children?.map(getSerializedNodeText).join("\n") || "";
|
|
11158
|
+
};
|
|
11159
|
+
const getSingleCellHTMLText = (html) => {
|
|
11160
|
+
if (!isSingleCellHTMLTable(html) || typeof DOMParser === "undefined") return void 0;
|
|
11161
|
+
const cell = new DOMParser().parseFromString(html, "text/html").body.querySelector("td, th");
|
|
11162
|
+
if (!cell) return void 0;
|
|
11163
|
+
cell.querySelectorAll("br").forEach((node) => node.replaceWith("\n"));
|
|
11164
|
+
return Array.from(cell.children).map((node) => node.textContent || "").join("\n") || cell.textContent || "";
|
|
11165
|
+
};
|
|
11166
|
+
const isSingleCellHTMLTable = (html) => {
|
|
11167
|
+
if (!html || typeof DOMParser === "undefined") return false;
|
|
11168
|
+
const document = new DOMParser().parseFromString(html, "text/html");
|
|
11169
|
+
const tables = document.body.querySelectorAll("table");
|
|
11170
|
+
if (tables.length !== 1 || tables[0].querySelectorAll("td, th").length !== 1) return false;
|
|
11171
|
+
const remainder = document.body.cloneNode(true);
|
|
11172
|
+
remainder.querySelector("table")?.remove();
|
|
11173
|
+
remainder.querySelectorAll("link, meta, script, style").forEach((node) => node.remove());
|
|
11174
|
+
return !remainder.querySelector("audio, canvas, iframe, img, object, svg, video") && remainder.textContent?.trim() === "";
|
|
11175
|
+
};
|
|
11176
|
+
const isSingleCellTableClipboard = (clipboardData) => {
|
|
11177
|
+
const lexicalResult = parseLexicalTableClipboard(clipboardData.getData(LEXICAL_CLIPBOARD_TYPE));
|
|
11178
|
+
if (lexicalResult !== void 0) return lexicalResult.isSingleCell;
|
|
11179
|
+
return isSingleCellHTMLTable(clipboardData.getData("text/html"));
|
|
11180
|
+
};
|
|
11181
|
+
const getSingleCellPlainText = (clipboardData) => {
|
|
11182
|
+
const lexicalResult = parseLexicalTableClipboard(clipboardData.getData(LEXICAL_CLIPBOARD_TYPE));
|
|
11183
|
+
if (lexicalResult?.cell) return getSerializedCellText(lexicalResult.cell);
|
|
11184
|
+
const htmlText = getSingleCellHTMLText(clipboardData.getData("text/html"));
|
|
11185
|
+
if (htmlText !== void 0) return htmlText;
|
|
11186
|
+
return clipboardData.getData("text/plain").replace(/(?:\r\n|\n)$/, "");
|
|
11187
|
+
};
|
|
11188
|
+
const handleSingleCellTablePaste = (editor, event) => {
|
|
11189
|
+
if (!("clipboardData" in event)) return false;
|
|
11190
|
+
const clipboardData = event.clipboardData;
|
|
11191
|
+
if (!clipboardData || !isSingleCellTableClipboard(clipboardData)) return false;
|
|
11192
|
+
event.preventDefault();
|
|
11193
|
+
editor.dispatchCommand(CONTROLLED_TEXT_INSERTION_COMMAND, getSingleCellPlainText(clipboardData));
|
|
11194
|
+
return true;
|
|
11195
|
+
};
|
|
11196
|
+
const registerSingleCellTablePaste = (editor) => {
|
|
11197
|
+
return editor.registerCommand(PASTE_COMMAND, (event) => handleSingleCellTablePaste(editor, event), COMMAND_PRIORITY_HIGH);
|
|
11198
|
+
};
|
|
11199
|
+
//#endregion
|
|
8925
11200
|
//#region src/plugins/table/plugin/index.ts
|
|
8926
11201
|
const tableCellProcessor = (before, content, after) => {
|
|
8927
11202
|
return before + content.replace(/\n+$/, "").replaceAll(/\n+/g, "<br />") + after;
|
|
8928
11203
|
};
|
|
11204
|
+
const isRemovedTableRowDiff = (node) => node.getType() === "table-row-diff" && "getDiffType" in node && typeof node.getDiffType === "function" && node.getDiffType() === "remove";
|
|
8929
11205
|
function isHeadlessEditor(editor) {
|
|
8930
11206
|
return editor._headless === true;
|
|
8931
11207
|
}
|
|
@@ -8974,6 +11250,7 @@ const TablePlugin = class extends KernelPlugin {
|
|
|
8974
11250
|
}
|
|
8975
11251
|
onInit(editor) {
|
|
8976
11252
|
this.register(registerTableCellUnmergeTransform(editor));
|
|
11253
|
+
this.register(registerSingleCellTablePaste(editor));
|
|
8977
11254
|
if (!isHeadlessEditor(editor)) {
|
|
8978
11255
|
setScrollableTablesActive(editor, true);
|
|
8979
11256
|
this.register(registerTablePlugin(editor));
|
|
@@ -9220,7 +11497,7 @@ const TablePlugin = class extends KernelPlugin {
|
|
|
9220
11497
|
markdownService.registerMarkdownWriter(TableRowNode.getType(), (ctx, node) => {
|
|
9221
11498
|
const parent = node.getParent();
|
|
9222
11499
|
if (!$isTableNode(parent)) return;
|
|
9223
|
-
if (!node.
|
|
11500
|
+
if (!node.getPreviousSiblings().some((sibling) => !isRemovedTableRowDiff(sibling))) ctx.wrap("", `\n${parent.getColWidths()?.map(() => {
|
|
9224
11501
|
return "|:--";
|
|
9225
11502
|
}).join("")}|\n`);
|
|
9226
11503
|
else ctx.wrap("", "\n");
|
|
@@ -9267,6 +11544,207 @@ const TablePlugin = class extends KernelPlugin {
|
|
|
9267
11544
|
}
|
|
9268
11545
|
};
|
|
9269
11546
|
//#endregion
|
|
11547
|
+
//#region src/plugins/collapsible/node/CollapsibleNode.ts
|
|
11548
|
+
const CONTENT_SELECTOR = "[data-collapsible-content=\"true\"]";
|
|
11549
|
+
const TOGGLE_SELECTOR = "[data-collapsible-toggle=\"true\"]";
|
|
11550
|
+
var CollapsibleNode = class CollapsibleNode extends ElementNode {
|
|
11551
|
+
static getType() {
|
|
11552
|
+
return "collapsible";
|
|
11553
|
+
}
|
|
11554
|
+
static clone(node) {
|
|
11555
|
+
return new CollapsibleNode(node.__title, node.__collapsed, node.__key);
|
|
11556
|
+
}
|
|
11557
|
+
static importDOM() {
|
|
11558
|
+
return { details: () => ({
|
|
11559
|
+
conversion: $convertDetailsElement,
|
|
11560
|
+
priority: 1
|
|
11561
|
+
}) };
|
|
11562
|
+
}
|
|
11563
|
+
static importJSON(serializedNode) {
|
|
11564
|
+
const node = $createCollapsibleNode(serializedNode.title || "Details").updateFromJSON(serializedNode);
|
|
11565
|
+
if (shouldPrependTitle(serializedNode)) {
|
|
11566
|
+
const titleParagraph = $createTitleParagraph(serializedNode.title || "Details");
|
|
11567
|
+
const firstChild = node.getFirstChild();
|
|
11568
|
+
if (firstChild) firstChild.insertBefore(titleParagraph);
|
|
11569
|
+
else node.append(titleParagraph);
|
|
11570
|
+
}
|
|
11571
|
+
return node;
|
|
11572
|
+
}
|
|
11573
|
+
constructor(title = "Details", collapsed = false, key) {
|
|
11574
|
+
super(key);
|
|
11575
|
+
this.__title = title;
|
|
11576
|
+
this.__collapsed = collapsed;
|
|
11577
|
+
}
|
|
11578
|
+
createDOM(config, editor) {
|
|
11579
|
+
const element = document.createElement("section");
|
|
11580
|
+
addClassNamesToElement(element, config.theme.collapsible);
|
|
11581
|
+
element.dataset.collapsible = "true";
|
|
11582
|
+
element.dataset.collapsibleCollapsed = String(this.__collapsed);
|
|
11583
|
+
const toggle = document.createElement("button");
|
|
11584
|
+
toggle.type = "button";
|
|
11585
|
+
toggle.contentEditable = "false";
|
|
11586
|
+
toggle.dataset.collapsibleToggle = "true";
|
|
11587
|
+
toggle.setAttribute("aria-expanded", String(!this.__collapsed));
|
|
11588
|
+
toggle.setAttribute("aria-label", this.__collapsed ? "Expand collapsible block" : "Collapse collapsible block");
|
|
11589
|
+
toggle.addEventListener("click", () => {
|
|
11590
|
+
editor.update(() => {
|
|
11591
|
+
const latest = this.getLatest();
|
|
11592
|
+
if ($isCollapsibleNode(latest)) latest.setCollapsed(!latest.isCollapsed());
|
|
11593
|
+
});
|
|
11594
|
+
});
|
|
11595
|
+
const content = document.createElement("div");
|
|
11596
|
+
content.dataset.collapsibleContent = "true";
|
|
11597
|
+
element.append(toggle, content);
|
|
11598
|
+
return element;
|
|
11599
|
+
}
|
|
11600
|
+
updateDOM(prevNode, dom) {
|
|
11601
|
+
const toggle = dom.querySelector(TOGGLE_SELECTOR);
|
|
11602
|
+
dom.dataset.collapsibleCollapsed = String(this.__collapsed);
|
|
11603
|
+
if (toggle && prevNode.__collapsed !== this.__collapsed) {
|
|
11604
|
+
toggle.setAttribute("aria-expanded", String(!this.__collapsed));
|
|
11605
|
+
toggle.setAttribute("aria-label", this.__collapsed ? "Expand collapsible block" : "Collapse collapsible block");
|
|
11606
|
+
}
|
|
11607
|
+
return false;
|
|
11608
|
+
}
|
|
11609
|
+
exportDOM() {
|
|
11610
|
+
const element = document.createElement("details");
|
|
11611
|
+
if (!this.__collapsed) element.setAttribute("open", "");
|
|
11612
|
+
const summary = document.createElement("summary");
|
|
11613
|
+
summary.textContent = this.getTitle();
|
|
11614
|
+
element.append(summary);
|
|
11615
|
+
return { element };
|
|
11616
|
+
}
|
|
11617
|
+
exportJSON() {
|
|
11618
|
+
return {
|
|
11619
|
+
...super.exportJSON(),
|
|
11620
|
+
collapsed: this.isCollapsed(),
|
|
11621
|
+
title: this.getTitle()
|
|
11622
|
+
};
|
|
11623
|
+
}
|
|
11624
|
+
getDOMSlot(element) {
|
|
11625
|
+
const content = element.querySelector(CONTENT_SELECTOR);
|
|
11626
|
+
if (content instanceof HTMLElement) return super.getDOMSlot(element).withElement(content);
|
|
11627
|
+
return super.getDOMSlot(element);
|
|
11628
|
+
}
|
|
11629
|
+
getTitle() {
|
|
11630
|
+
const latest = this.getLatest();
|
|
11631
|
+
const firstChild = latest.getFirstChild();
|
|
11632
|
+
return firstChild ? firstChild.getTextContent().trim() : latest.__title;
|
|
11633
|
+
}
|
|
11634
|
+
isCollapsed() {
|
|
11635
|
+
return this.getLatest().__collapsed;
|
|
11636
|
+
}
|
|
11637
|
+
isShadowRoot() {
|
|
11638
|
+
return true;
|
|
11639
|
+
}
|
|
11640
|
+
setCollapsed(collapsed) {
|
|
11641
|
+
const writable = this.getWritable();
|
|
11642
|
+
writable.__collapsed = collapsed;
|
|
11643
|
+
return writable;
|
|
11644
|
+
}
|
|
11645
|
+
setTitle(title) {
|
|
11646
|
+
const writable = this.getWritable();
|
|
11647
|
+
writable.__title = title;
|
|
11648
|
+
return writable;
|
|
11649
|
+
}
|
|
11650
|
+
updateFromJSON(serializedNode) {
|
|
11651
|
+
return super.updateFromJSON(serializedNode).setTitle(serializedNode.title || "Details").setCollapsed(Boolean(serializedNode.collapsed));
|
|
11652
|
+
}
|
|
11653
|
+
};
|
|
11654
|
+
function $createCollapsibleNode(title = "Details", collapsed = false) {
|
|
11655
|
+
return $applyNodeReplacement(new CollapsibleNode(title, collapsed));
|
|
11656
|
+
}
|
|
11657
|
+
function $isCollapsibleNode(node) {
|
|
11658
|
+
return node instanceof CollapsibleNode;
|
|
11659
|
+
}
|
|
11660
|
+
function $convertDetailsElement(domNode) {
|
|
11661
|
+
if (!(domNode instanceof HTMLDetailsElement)) return { node: null };
|
|
11662
|
+
return { node: $createCollapsibleNode(domNode.querySelector("summary")?.textContent?.trim() || "Details", !domNode.open) };
|
|
11663
|
+
}
|
|
11664
|
+
function $createTitleParagraph(title) {
|
|
11665
|
+
const paragraph = $createParagraphNode();
|
|
11666
|
+
paragraph.append($createTextNode(title));
|
|
11667
|
+
return paragraph;
|
|
11668
|
+
}
|
|
11669
|
+
function getSerializedText(node) {
|
|
11670
|
+
if (!node || typeof node !== "object") return "";
|
|
11671
|
+
if ("text" in node && typeof node.text === "string") return node.text;
|
|
11672
|
+
if ("children" in node && Array.isArray(node.children)) return node.children.map((child) => getSerializedText(child)).join("");
|
|
11673
|
+
return "";
|
|
11674
|
+
}
|
|
11675
|
+
function shouldPrependTitle(serializedNode) {
|
|
11676
|
+
const title = serializedNode.title?.trim();
|
|
11677
|
+
if (!title) return false;
|
|
11678
|
+
const firstChild = (Array.isArray(serializedNode.children) ? serializedNode.children : [])[0];
|
|
11679
|
+
return getSerializedText(firstChild).trim() !== title;
|
|
11680
|
+
}
|
|
11681
|
+
//#endregion
|
|
11682
|
+
//#region src/headless/collapsible-plugin.ts
|
|
11683
|
+
const HeadlessCollapsiblePlugin = class extends KernelPlugin {
|
|
11684
|
+
static {
|
|
11685
|
+
this.pluginName = "HeadlessCollapsiblePlugin";
|
|
11686
|
+
}
|
|
11687
|
+
constructor(kernel) {
|
|
11688
|
+
super();
|
|
11689
|
+
this.kernel = kernel;
|
|
11690
|
+
kernel.registerNodes([CollapsibleNode]);
|
|
11691
|
+
}
|
|
11692
|
+
onInit(_editor) {
|
|
11693
|
+
const markdownService = this.kernel.requireService(IMarkdownShortCutService);
|
|
11694
|
+
markdownService?.registerMarkdownWriter(CollapsibleNode.getType(), (ctx, node) => {
|
|
11695
|
+
if (!$isCollapsibleNode(node)) return false;
|
|
11696
|
+
ctx.appendLine(`<details${node.isCollapsed() ? "" : " open"}>\n`);
|
|
11697
|
+
ctx.appendLine(`<summary>${escapeHtml(node.getTitle())}</summary>\n\n`);
|
|
11698
|
+
node.getChildren().slice(1).forEach((child) => ctx.processChild(ctx, child));
|
|
11699
|
+
ctx.appendLine("\n</details>\n");
|
|
11700
|
+
return true;
|
|
11701
|
+
});
|
|
11702
|
+
markdownService?.registerMarkdownReader("html", (node, children) => {
|
|
11703
|
+
const tag = getHtmlTagName(node.value);
|
|
11704
|
+
if (tag === "summary") return INodeHelper.createTypeNode("__collapsible_summary", { text: getNodeText(children) });
|
|
11705
|
+
if (tag !== "details") return false;
|
|
11706
|
+
const summaryNode = children.find((child) => child.type === "__collapsible_summary");
|
|
11707
|
+
const contentChildren = children.filter((child) => child.type !== "__collapsible_summary");
|
|
11708
|
+
const title = summaryNode && "text" in summaryNode && typeof summaryNode.text === "string" ? summaryNode.text : extractSummaryTitle(node.value) || "Details";
|
|
11709
|
+
return INodeHelper.createElementNode(CollapsibleNode.getType(), {
|
|
11710
|
+
children: ensureTitleChildren(contentChildren, title),
|
|
11711
|
+
collapsed: !/\sopen(?:\s|>|$)/i.test(node.value),
|
|
11712
|
+
title
|
|
11713
|
+
});
|
|
11714
|
+
});
|
|
11715
|
+
const litexmlService = this.kernel.requireService(ILitexmlService);
|
|
11716
|
+
litexmlService?.registerXMLWriter(CollapsibleNode.getType(), (node, ctx) => {
|
|
11717
|
+
if (!$isCollapsibleNode(node)) return false;
|
|
11718
|
+
return ctx.createXmlNode("collapsible", {
|
|
11719
|
+
collapsed: String(node.isCollapsed()),
|
|
11720
|
+
title: node.getTitle()
|
|
11721
|
+
});
|
|
11722
|
+
});
|
|
11723
|
+
litexmlService?.registerXMLReader("collapsible", (xmlNode, children) => {
|
|
11724
|
+
const title = xmlNode.getAttribute("title") || "Details";
|
|
11725
|
+
return INodeHelper.createElementNode(CollapsibleNode.getType(), {
|
|
11726
|
+
children: ensureTitleChildren(children, title),
|
|
11727
|
+
collapsed: xmlNode.getAttribute("collapsed") === "true",
|
|
11728
|
+
title
|
|
11729
|
+
});
|
|
11730
|
+
});
|
|
11731
|
+
}
|
|
11732
|
+
};
|
|
11733
|
+
const getHtmlTagName = (value) => value.match(/^<\/?\s*([\da-z-]+)/i)?.[1]?.toLowerCase() || "";
|
|
11734
|
+
const escapeHtml = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """);
|
|
11735
|
+
const extractSummaryTitle = (value) => value.match(/<summary\b[^>]*>([\S\s]*?)<\/summary>/i)?.[1]?.trim() || "";
|
|
11736
|
+
const getNodeText = (children) => children.map((child) => {
|
|
11737
|
+
if (!child || typeof child !== "object") return "";
|
|
11738
|
+
if ("text" in child && typeof child.text === "string") return child.text;
|
|
11739
|
+
if ("children" in child && Array.isArray(child.children)) return getNodeText(child.children);
|
|
11740
|
+
return "";
|
|
11741
|
+
}).join("");
|
|
11742
|
+
const ensureTitleChildren = (children, title) => {
|
|
11743
|
+
const trimmedTitle = title.trim();
|
|
11744
|
+
if (!trimmedTitle || getNodeText(children.slice(0, 1)).trim() === trimmedTitle) return children;
|
|
11745
|
+
return [INodeHelper.createParagraph({ children: [INodeHelper.createTextNode(title)] }), ...children];
|
|
11746
|
+
};
|
|
11747
|
+
//#endregion
|
|
9270
11748
|
//#region src/headless/extract-media-from-editor-state.ts
|
|
9271
11749
|
const IMAGE_TYPES = /* @__PURE__ */ new Set(["image", "block-image"]);
|
|
9272
11750
|
const FILE_TYPE = "file";
|
|
@@ -9416,6 +11894,7 @@ const DEFAULT_HEADLESS_EDITOR_PLUGINS = [
|
|
|
9416
11894
|
HRPlugin,
|
|
9417
11895
|
ListPlugin,
|
|
9418
11896
|
TablePlugin,
|
|
11897
|
+
HeadlessCollapsiblePlugin,
|
|
9419
11898
|
LitexmlPlugin
|
|
9420
11899
|
];
|
|
9421
11900
|
var HeadlessEditor = class {
|