@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.
Files changed (148) hide show
  1. package/es/codemirror/style.js +42 -0
  2. package/es/editor-kernel/kernel.d.ts +9 -0
  3. package/es/editor-kernel/kernel.js +74 -11
  4. package/es/editor-kernel/react/useDecorators.js +2 -4
  5. package/es/headless/collapsible-plugin.js +74 -0
  6. package/es/headless/index.js +2 -0
  7. package/es/headless.d.ts +30 -0
  8. package/es/headless.js +2618 -139
  9. package/es/index.d.ts +26 -3
  10. package/es/index.js +17 -2
  11. package/es/locale/index.d.ts +14 -0
  12. package/es/locale/index.js +14 -0
  13. package/es/plugins/block/command/index.js +24 -0
  14. package/es/plugins/block/plugin/index.js +7 -5
  15. package/es/plugins/block/react/ReactBlockPlugin.js +43 -1
  16. package/es/plugins/block/react/core/menu-position.js +20 -0
  17. package/es/plugins/block/react/drag/drag-session.js +3 -2
  18. package/es/plugins/block/react/drag/drag-utils.js +13 -1
  19. package/es/plugins/codemirror-block/index.d.ts +2 -1
  20. package/es/plugins/codemirror-block/plugin/index.js +2 -0
  21. package/es/plugins/codemirror-block/react/CodemirrorNode.js +95 -5
  22. package/es/plugins/codemirror-block/react/useCodemirrorEditLock.js +33 -0
  23. package/es/plugins/codemirror-block/service/index.d.ts +40 -0
  24. package/es/plugins/codemirror-block/service/index.js +59 -0
  25. package/es/plugins/collapsible/command/index.d.ts +17 -0
  26. package/es/plugins/collapsible/command/index.js +72 -0
  27. package/es/plugins/collapsible/index.d.ts +7 -0
  28. package/es/plugins/collapsible/node/CollapsibleNode.d.ts +30 -0
  29. package/es/plugins/collapsible/node/CollapsibleNode.js +138 -0
  30. package/es/plugins/collapsible/plugin/index.d.ts +11 -0
  31. package/es/plugins/collapsible/plugin/index.js +535 -0
  32. package/es/plugins/collapsible/react/ReactCollapsiblePlugin.d.ts +6 -0
  33. package/es/plugins/collapsible/react/ReactCollapsiblePlugin.js +19 -0
  34. package/es/plugins/collapsible/react/index.d.ts +3 -0
  35. package/es/plugins/collapsible/react/style.js +67 -0
  36. package/es/plugins/collapsible/react/type.d.ts +6 -0
  37. package/es/plugins/common/plugin/index.js +14 -4
  38. package/es/plugins/common/plugin/progressive-select-all.js +81 -0
  39. package/es/plugins/common/plugin/register.js +73 -4
  40. package/es/plugins/common/react/ReactPlainText.js +27 -9
  41. package/es/plugins/link/command/index.js +26 -3
  42. package/es/plugins/link/conversion/index.js +237 -0
  43. package/es/plugins/link/index.d.ts +5 -3
  44. package/es/plugins/link/node/LinkBlockCardNode.d.ts +16 -0
  45. package/es/plugins/link/node/LinkBlockCardNode.js +66 -0
  46. package/es/plugins/link/node/LinkCardNode.d.ts +42 -0
  47. package/es/plugins/link/node/LinkCardNode.js +161 -0
  48. package/es/plugins/link/node/LinkIframeNode.d.ts +33 -0
  49. package/es/plugins/link/node/LinkIframeNode.js +131 -0
  50. package/es/plugins/link/node/LinkNode.d.ts +48 -2
  51. package/es/plugins/link/node/LinkNode.js +14 -6
  52. package/es/plugins/link/node/SchemaNode.d.ts +36 -0
  53. package/es/plugins/link/node/SchemaNode.js +149 -0
  54. package/es/plugins/link/normalization/index.d.ts +7 -0
  55. package/es/plugins/link/normalization/index.js +38 -0
  56. package/es/plugins/link/plugin/index.d.ts +26 -1
  57. package/es/plugins/link/plugin/index.js +124 -4
  58. package/es/plugins/link/react/ReactLinkPlugin.js +165 -7
  59. package/es/plugins/link/react/components/LinkCard.js +235 -0
  60. package/es/plugins/link/react/components/LinkEdit.js +93 -14
  61. package/es/plugins/link/react/components/LinkIframe.js +128 -0
  62. package/es/plugins/link/react/components/LinkToolbar.js +440 -111
  63. package/es/plugins/link/react/components/SchemaLink.js +49 -0
  64. package/es/plugins/link/react/index.d.ts +2 -2
  65. package/es/plugins/link/react/renderer-registry.d.ts +81 -0
  66. package/es/plugins/link/react/renderer-registry.js +80 -0
  67. package/es/plugins/link/react/style.js +63 -1
  68. package/es/plugins/link/react/type.d.ts +24 -1
  69. package/es/plugins/link/service/i-link-service.d.ts +156 -1
  70. package/es/plugins/link/service/i-link-service.js +258 -5
  71. package/es/plugins/list/plugin/index.js +9 -2
  72. package/es/plugins/litexml/command/diffCommand.js +94 -14
  73. package/es/plugins/litexml/command/index.js +187 -6
  74. package/es/plugins/litexml/diff-validation.js +237 -0
  75. package/es/plugins/litexml/node/DiffContentNode.js +67 -0
  76. package/es/plugins/litexml/node/DiffNode.d.ts +1 -1
  77. package/es/plugins/litexml/node/DiffNode.js +5 -2
  78. package/es/plugins/litexml/node/TableCellDiffNode.js +77 -0
  79. package/es/plugins/litexml/node/TableRowDiffNode.js +73 -0
  80. package/es/plugins/litexml/plugin/index.d.ts +2 -0
  81. package/es/plugins/litexml/plugin/index.js +62 -9
  82. package/es/plugins/litexml/react/DiffNodeToolbar/index.js +10 -12
  83. package/es/plugins/litexml/react/TableRowDiffToolbar.js +123 -0
  84. package/es/plugins/litexml/react/hooks/useHasDiffNode.js +5 -2
  85. package/es/plugins/litexml/react/index.js +6 -2
  86. package/es/plugins/litexml/react/style.js +18 -5
  87. package/es/plugins/litexml/table-cell-diff.js +153 -0
  88. package/es/plugins/litexml/table-row-diff.js +82 -0
  89. package/es/plugins/markdown/data-source/markdown/parse.js +25 -1
  90. package/es/plugins/markdown/plugin/index.js +5 -3
  91. package/es/plugins/slash/react/ReactSlashPlugin.js +22 -43
  92. package/es/plugins/slash/react/components/DefaultSlashMenu.js +76 -16
  93. package/es/plugins/slash/react/menu-utils.js +84 -0
  94. package/es/plugins/slash/service/i-slash-service.d.ts +3 -0
  95. package/es/plugins/table/plugin/index.js +4 -1
  96. package/es/plugins/table/plugin/single-cell-paste.js +79 -0
  97. package/es/plugins/table/react/TableController/hooks.js +7 -4
  98. package/es/plugins/table/react/hooks.js +2 -2
  99. package/es/plugins/table/react/index.js +2 -0
  100. package/es/plugins/table/react/style.js +15 -5
  101. package/es/plugins/table/react/useScrollFocusedTableCellIntoView.js +46 -0
  102. package/es/plugins/toc/index.d.ts +10 -0
  103. package/es/plugins/toc/plugin/index.d.ts +8 -0
  104. package/es/plugins/toc/plugin/index.js +56 -0
  105. package/es/plugins/toc/react/ReactTocPlugin.d.ts +6 -0
  106. package/es/plugins/toc/react/ReactTocPlugin.js +36 -0
  107. package/es/plugins/toc/react/getNearestScrollContainer.d.ts +6 -0
  108. package/es/plugins/toc/react/getNearestScrollContainer.js +18 -0
  109. package/es/plugins/toc/react/hooks/useActiveHeading.js +59 -0
  110. package/es/plugins/toc/react/hooks/useTocItems.js +50 -0
  111. package/es/plugins/toc/react/index.d.ts +5 -0
  112. package/es/plugins/toc/react/type.d.ts +30 -0
  113. package/es/plugins/toc/react/useToc.d.ts +5 -0
  114. package/es/plugins/toc/react/useToc.js +41 -0
  115. package/es/plugins/toc/service/i-toc-service.d.ts +39 -0
  116. package/es/plugins/toc/service/i-toc-service.js +125 -0
  117. package/es/plugins/toc/service/index.d.ts +3 -0
  118. package/es/plugins/toc/types.d.ts +23 -0
  119. package/es/plugins/toc/utils/index.d.ts +6 -0
  120. package/es/plugins/toc/utils/index.js +30 -0
  121. package/es/plugins/toolbar/react/index.js +26 -12
  122. package/es/plugins/toolbar/react/style.js +3 -2
  123. package/es/plugins/virtual-block/plugin/register.js +34 -12
  124. package/es/plugins/yjs/index.d.ts +6 -0
  125. package/es/plugins/yjs/plugin/index.d.ts +7 -0
  126. package/es/plugins/yjs/plugin/index.js +191 -0
  127. package/es/plugins/yjs/plugin/types.d.ts +16 -0
  128. package/es/plugins/yjs/plugin/utils/awareness.js +9 -0
  129. package/es/plugins/yjs/plugin/utils/editor-state.js +47 -0
  130. package/es/plugins/yjs/plugin/utils/history.js +31 -0
  131. package/es/plugins/yjs/plugin/utils/node-properties.js +39 -0
  132. package/es/plugins/yjs/plugin/utils/sync.js +24 -0
  133. package/es/plugins/yjs/react/codemirrorEditLockProvider.js +59 -0
  134. package/es/plugins/yjs/react/index.d.ts +23 -0
  135. package/es/plugins/yjs/react/index.js +200 -0
  136. package/es/plugins/yjs/service/index.d.ts +47 -0
  137. package/es/plugins/yjs/service/index.js +84 -0
  138. package/es/plugins/yjs/websocket-provider.d.ts +78 -0
  139. package/es/plugins/yjs/websocket-provider.js +263 -0
  140. package/es/react/hooks/useEditor.d.ts +24 -2
  141. package/es/react/hooks/useEditor.js +13 -3
  142. package/es/react.d.ts +2 -2
  143. package/es/renderer/LexicalRenderer.js +9 -2
  144. package/es/types/kernel.d.ts +16 -0
  145. package/es/utils/updatePosition.js +5 -2
  146. package/package.json +3 -1
  147. package/patches/@lexical__yjs@0.42.0.patch +42 -0
  148. package/scripts/postinstall-lexical-patch.cjs +62 -36
@@ -0,0 +1,237 @@
1
+ //#region src/plugins/litexml/diff-validation.ts
2
+ const ACTIONABLE_DIFF_TYPES = /* @__PURE__ */ new Set([
3
+ "add",
4
+ "remove",
5
+ "modify",
6
+ "listItemAdd",
7
+ "listItemModify",
8
+ "listItemRemove"
9
+ ]);
10
+ const isActionableDiff = (node) => node?.type === "diff" && ACTIONABLE_DIFF_TYPES.has(node.diffType || "");
11
+ const isElementNode = (node) => Array.isArray(node.children);
12
+ const cloneNode = (node) => structuredClone(node);
13
+ function hasActionableDiffDescendant(node) {
14
+ return (node.children || []).some((child) => isActionableDiff(child) || hasActionableDiffDescendant(child));
15
+ }
16
+ /**
17
+ * Validate the serialized public tree. Paths use serialized node ids, not
18
+ * Lexical's private node map, so this remains stable across JSON reloads.
19
+ */
20
+ function collectIllegalNestedDiffPaths(root) {
21
+ const violations = [];
22
+ const visit = (node, ancestors, path) => {
23
+ const actionableAncestor = [...ancestors].reverse().find(isActionableDiff);
24
+ const rowDiffAncestor = [...ancestors].reverse().find((ancestor) => ancestor.type === "table-row-diff");
25
+ const allowedCellDiffChild = ancestors.at(-1)?.type === "table-cell-diff" && isActionableDiff(node);
26
+ if (isActionableDiff(node) && actionableAncestor && !allowedCellDiffChild) violations.push(`${path}:diff-inside-diff`);
27
+ if (rowDiffAncestor) {
28
+ if (node.type === "table-cell-diff") violations.push(`${path}:cell-inside-row-diff`);
29
+ else if (isActionableDiff(node)) violations.push(`${path}:diff-inside-row-diff`);
30
+ }
31
+ if (actionableAncestor) {
32
+ if (node.type === "table-cell-diff") violations.push(`${path}:cell-diff-inside-diff`);
33
+ else if (node.type === "table-row-diff") violations.push(`${path}:row-diff-inside-diff`);
34
+ }
35
+ if (!isElementNode(node)) return;
36
+ node.children?.forEach((child) => {
37
+ visit(child, [...ancestors, node], `${path}/${child.id || child.type || "node"}`);
38
+ });
39
+ };
40
+ visit(root, [], root.id || root.type || "root");
41
+ return violations;
42
+ }
43
+ function findNewIllegalDiffPaths(previous, projected) {
44
+ const previousCounts = /* @__PURE__ */ new Map();
45
+ collectIllegalNestedDiffPaths(previous.root).forEach((path) => {
46
+ previousCounts.set(path, (previousCounts.get(path) || 0) + 1);
47
+ });
48
+ const newPaths = [];
49
+ const projectedCounts = /* @__PURE__ */ new Map();
50
+ collectIllegalNestedDiffPaths(projected.root).forEach((path) => {
51
+ const count = (projectedCounts.get(path) || 0) + 1;
52
+ projectedCounts.set(path, count);
53
+ if (count > (previousCounts.get(path) || 0)) newPaths.push(path);
54
+ });
55
+ return newPaths;
56
+ }
57
+ function findNodeLocation(root, id, ancestors = []) {
58
+ if (root.id === id) return {
59
+ ancestors,
60
+ index: -1,
61
+ node: root,
62
+ parent: null
63
+ };
64
+ for (const [index, child] of (root.children || []).entries()) {
65
+ if (child.id === id) return {
66
+ ancestors: [...ancestors, root],
67
+ index,
68
+ node: child,
69
+ parent: root
70
+ };
71
+ const nested = findNodeLocation(child, id, [...ancestors, root]);
72
+ if (nested) return nested;
73
+ }
74
+ return null;
75
+ }
76
+ function replaceNode(location, replacements) {
77
+ if (!location.parent || location.index < 0) return;
78
+ location.parent.children?.splice(location.index, 1, ...replacements);
79
+ }
80
+ function createDiff(diffType, children) {
81
+ return {
82
+ children,
83
+ diffType,
84
+ type: "diff"
85
+ };
86
+ }
87
+ function createDiffContent(side, children) {
88
+ return {
89
+ children,
90
+ side,
91
+ type: "diff-content"
92
+ };
93
+ }
94
+ function projectModifyTarget(root, incoming) {
95
+ if (!incoming.id) return;
96
+ const location = findNodeLocation(root, incoming.id);
97
+ if (!location) return;
98
+ const target = location.node;
99
+ const next = cloneNode(incoming);
100
+ if (target.type === "tablerow" && location.parent?.type === "table") {
101
+ replaceNode(location, [{
102
+ children: cloneNode(target.children || []),
103
+ diffType: "remove",
104
+ type: "table-row-diff"
105
+ }, {
106
+ children: cloneNode(next.children || []),
107
+ diffType: "add",
108
+ type: "table-row-diff"
109
+ }]);
110
+ return;
111
+ }
112
+ if (target.type === "tablecell" || target.type === "table-cell-diff") {
113
+ const existingDiff = (target.children || []).find((child) => child.type === "diff");
114
+ if (existingDiff && target.type === "table-cell-diff") {
115
+ existingDiff.children = cloneNode(next.children || []);
116
+ return;
117
+ }
118
+ const after = existingDiff?.children?.find((child) => child.type === "diff-content" && child.side === "after");
119
+ if (existingDiff?.diffType === "modify" && after) {
120
+ after.children = cloneNode(next.children || []);
121
+ return;
122
+ }
123
+ target.children = [createDiff("modify", [createDiffContent("before", cloneNode(target.children || [])), createDiffContent("after", cloneNode(next.children || []))])];
124
+ return;
125
+ }
126
+ if (location.ancestors.some(isActionableDiff)) {
127
+ replaceNode(location, [next]);
128
+ return;
129
+ }
130
+ replaceNode(location, [createDiff("modify", [cloneNode(target), next])]);
131
+ }
132
+ function projectRemoveTarget(root, id) {
133
+ const location = findNodeLocation(root, id);
134
+ if (!location) return;
135
+ if (location.ancestors.some(isActionableDiff)) {
136
+ replaceNode(location, []);
137
+ return;
138
+ }
139
+ const target = location.node;
140
+ if (target.type === "tablecell" || target.type === "table-cell-diff") {
141
+ replaceNode(location, [{
142
+ children: [createDiff("remove", cloneNode(target.children || []))],
143
+ diffType: "remove",
144
+ type: "table-cell-diff"
145
+ }]);
146
+ return;
147
+ }
148
+ if (target.type === "tablerow" && location.parent?.type === "table") {
149
+ replaceNode(location, [{
150
+ children: cloneNode(target.children || []),
151
+ diffType: "remove",
152
+ type: "table-row-diff"
153
+ }]);
154
+ return;
155
+ }
156
+ replaceNode(location, [createDiff("remove", [cloneNode(target)])]);
157
+ }
158
+ function projectInsert(root, operation, readXml) {
159
+ const referenceId = "beforeId" in operation ? operation.beforeId : operation.afterId;
160
+ const reference = referenceId === "root" ? null : findNodeLocation(root, referenceId)?.node || null;
161
+ const location = referenceId === "root" ? {
162
+ ancestors: [root],
163
+ index: "beforeId" in operation ? 0 : (root.children || []).length - 1,
164
+ node: root.children?.[0] || root,
165
+ parent: root
166
+ } : findNodeLocation(root, referenceId);
167
+ if (!location) return;
168
+ const incoming = (readXml(operation.litexml).root?.children || []).map(cloneNode);
169
+ if (incoming.length === 0 || !location.parent) return;
170
+ const insertAt = "beforeId" in operation ? location.index : location.index + 1;
171
+ if (incoming.every((node) => node.type === "tablecell") && reference?.type === "tablecell") {
172
+ location.parent.children?.splice(insertAt, 0, ...incoming.map((node) => ({
173
+ children: [createDiff("add", cloneNode(node.children || []))],
174
+ diffType: "add",
175
+ type: "table-cell-diff"
176
+ })));
177
+ return;
178
+ }
179
+ if (incoming.every((node) => node.type === "tablerow") && reference?.type === "tablerow") {
180
+ location.parent.children?.splice(insertAt, 0, ...incoming.map((node) => ({
181
+ children: cloneNode(node.children || []),
182
+ diffType: "add",
183
+ type: "table-row-diff"
184
+ })));
185
+ return;
186
+ }
187
+ const actionableAncestor = location.ancestors.findLast(isActionableDiff);
188
+ const isInlineReference = [
189
+ "text",
190
+ "span",
191
+ "link",
192
+ "autolink",
193
+ "linebreak"
194
+ ].includes(reference?.type || "");
195
+ const blockAncestor = [...location.ancestors].reverse().find((node) => [
196
+ "paragraph",
197
+ "heading",
198
+ "listitem",
199
+ "table",
200
+ "tablerow",
201
+ "tablecell"
202
+ ].includes(node.type || ""));
203
+ if (!actionableAncestor && blockAncestor && hasActionableDiffDescendant(blockAncestor)) {
204
+ const wrapped = createDiff("modify", [cloneNode(blockAncestor), cloneNode(blockAncestor)]);
205
+ const blockLocation = findNodeLocation(root, blockAncestor.id || "");
206
+ if (blockLocation) replaceNode(blockLocation, [wrapped]);
207
+ return;
208
+ }
209
+ if (actionableAncestor && !isInlineReference) {
210
+ const originLocation = actionableAncestor.id ? findNodeLocation(root, actionableAncestor.id) : null;
211
+ if (originLocation?.parent && originLocation.index >= 0) {
212
+ const originInsertAt = "beforeId" in operation ? originLocation.index : originLocation.index + 1;
213
+ originLocation.parent.children?.splice(originInsertAt, 0, ...incoming.map((node) => createDiff("add", [node])));
214
+ return;
215
+ }
216
+ }
217
+ const inserted = actionableAncestor && isInlineReference ? incoming : incoming.map((node) => createDiff("add", [node]));
218
+ location.parent.children?.splice(insertAt, 0, ...inserted);
219
+ }
220
+ /**
221
+ * Project one operation on a cloned serialized tree. The real Lexical tree is
222
+ * untouched; callers compare illegal paths before/after and only dispatch safe
223
+ * operations to the live editor.
224
+ */
225
+ function projectLiteXmlOperation(document, operation, readXml) {
226
+ const projected = cloneNode(document);
227
+ if (operation.action === "modify") (Array.isArray(operation.litexml) ? operation.litexml : [operation.litexml]).forEach((xml) => {
228
+ (readXml(xml).root?.children || []).forEach((child) => {
229
+ projectModifyTarget(projected.root, child);
230
+ });
231
+ });
232
+ else if (operation.action === "remove") projectRemoveTarget(projected.root, operation.id);
233
+ else projectInsert(projected.root, operation, readXml);
234
+ return projected;
235
+ }
236
+ //#endregion
237
+ export { collectIllegalNestedDiffPaths, findNewIllegalDiffPaths, hasActionableDiffDescendant, projectLiteXmlOperation };
@@ -0,0 +1,67 @@
1
+ import { $applyNodeReplacement, ElementNode } from "lexical";
2
+ //#region src/plugins/litexml/node/DiffContentNode.ts
3
+ /** Groups one side of a modify diff so it can contain any number of block nodes. */
4
+ var DiffContentNode = class DiffContentNode extends ElementNode {
5
+ static getType() {
6
+ return "diff-content";
7
+ }
8
+ static clone(node) {
9
+ return new DiffContentNode(node.__side, node.__key);
10
+ }
11
+ static importJSON(serializedNode) {
12
+ return $createDiffContentNode(serializedNode.side).updateFromJSON(serializedNode);
13
+ }
14
+ static importDOM() {
15
+ return null;
16
+ }
17
+ constructor(side, key) {
18
+ super(key);
19
+ this.__side = side;
20
+ }
21
+ get side() {
22
+ return this.__side;
23
+ }
24
+ setSide(side) {
25
+ this.getWritable().__side = side;
26
+ return this;
27
+ }
28
+ updateFromJSON(serializedNode) {
29
+ return super.updateFromJSON(serializedNode).setSide(serializedNode.side);
30
+ }
31
+ exportJSON() {
32
+ return {
33
+ ...super.exportJSON(),
34
+ side: this.__side,
35
+ type: DiffContentNode.getType(),
36
+ version: 1
37
+ };
38
+ }
39
+ createDOM(_config, _editor) {
40
+ const element = document.createElement("div");
41
+ element.dataset.diffContent = this.__side;
42
+ return element;
43
+ }
44
+ updateDOM(previousNode, dom) {
45
+ if (previousNode.__side !== this.__side) dom.dataset.diffContent = this.__side;
46
+ return false;
47
+ }
48
+ exportDOM(_editor) {
49
+ const element = document.createElement("div");
50
+ element.dataset.diffContent = this.__side;
51
+ return { element };
52
+ }
53
+ canBeEmpty() {
54
+ return true;
55
+ }
56
+ isInline() {
57
+ return false;
58
+ }
59
+ };
60
+ function $createDiffContentNode(side) {
61
+ return $applyNodeReplacement(new DiffContentNode(side));
62
+ }
63
+ function $isDiffContentNode(node) {
64
+ return node instanceof DiffContentNode;
65
+ }
66
+ //#endregion
67
+ export { $createDiffContentNode, $isDiffContentNode, DiffContentNode };
@@ -5,7 +5,7 @@ type DiffType = 'add' | 'remove' | 'modify' | 'unchanged' | 'listItemModify' | '
5
5
  type SerializedDiffNode = Spread<{
6
6
  diffType: DiffType;
7
7
  }, SerializedElementNode>;
8
- /** DiffNode - contains two block children: original and modified */
8
+ /** DiffNode - contains the original and modified sides, optionally grouped into multiple blocks. */
9
9
  declare class DiffNode extends CardLikeElementNode {
10
10
  static getType(): string;
11
11
  static clone(node: DiffNode): DiffNode;
@@ -3,7 +3,7 @@ import { CardLikeElementNode } from "../../common/node/cursor.js";
3
3
  import { $applyNodeReplacement } from "lexical";
4
4
  //#region src/plugins/litexml/node/DiffNode.ts
5
5
  init_utils();
6
- /** DiffNode - contains two block children: original and modified */
6
+ /** DiffNode - contains the original and modified sides, optionally grouped into multiple blocks. */
7
7
  var DiffNode = class DiffNode extends CardLikeElementNode {
8
8
  static getType() {
9
9
  return "diff";
@@ -95,5 +95,8 @@ var DiffNode = class DiffNode extends CardLikeElementNode {
95
95
  function $createDiffNode(diffType = "unchanged") {
96
96
  return $applyNodeReplacement(new DiffNode(diffType));
97
97
  }
98
+ function $isDiffNode(node) {
99
+ return node instanceof DiffNode;
100
+ }
98
101
  //#endregion
99
- export { $createDiffNode, DiffNode };
102
+ export { $createDiffNode, $isDiffNode, DiffNode };
@@ -0,0 +1,77 @@
1
+ import { TableCellNode } from "@lexical/table";
2
+ import { $applyNodeReplacement } from "lexical";
3
+ //#region src/plugins/litexml/node/TableCellDiffNode.ts
4
+ /**
5
+ * Review-only table cell. The diff state lives on a TableCellNode subclass so
6
+ * TableRowNode children remain cells throughout staging and approval.
7
+ */
8
+ var TableCellDiffNode = class TableCellDiffNode extends TableCellNode {
9
+ static getType() {
10
+ return "table-cell-diff";
11
+ }
12
+ static clone(node) {
13
+ const clone = new TableCellDiffNode(node.__diffType, node.__changeId, node.getHeaderStyles(), node.getColSpan(), node.getWidth(), node.__key);
14
+ clone.__rowSpan = node.__rowSpan;
15
+ clone.__backgroundColor = node.__backgroundColor;
16
+ clone.__verticalAlign = node.__verticalAlign;
17
+ return clone;
18
+ }
19
+ static importJSON(serializedNode) {
20
+ return $createTableCellDiffNode(serializedNode.diffType, serializedNode.changeId, serializedNode.headerState, serializedNode.colSpan, serializedNode.width).updateFromJSON(serializedNode);
21
+ }
22
+ constructor(diffType, changeId, headerState, colSpan, width, key) {
23
+ super(headerState, colSpan, width, key);
24
+ this.__diffType = diffType;
25
+ this.__changeId = changeId;
26
+ }
27
+ createDOM(config) {
28
+ const element = super.createDOM(config);
29
+ const theme = config.theme.tableCellDiff;
30
+ if (typeof theme === "string" && theme) element.classList.add(theme);
31
+ this.syncDOMState(element);
32
+ return element;
33
+ }
34
+ updateDOM(prevNode) {
35
+ return super.updateDOM(prevNode) || prevNode.__diffType !== this.__diffType || prevNode.__changeId !== this.__changeId;
36
+ }
37
+ exportJSON() {
38
+ return {
39
+ ...super.exportJSON(),
40
+ changeId: this.__changeId,
41
+ diffType: this.__diffType,
42
+ type: TableCellDiffNode.getType(),
43
+ version: 1
44
+ };
45
+ }
46
+ getChangeId() {
47
+ return this.getLatest().__changeId;
48
+ }
49
+ getDiffType() {
50
+ return this.getLatest().__diffType;
51
+ }
52
+ setChangeId(changeId) {
53
+ this.getWritable().__changeId = changeId;
54
+ return this;
55
+ }
56
+ setDiffType(diffType) {
57
+ this.getWritable().__diffType = diffType;
58
+ return this;
59
+ }
60
+ updateFromJSON(serializedNode) {
61
+ return super.updateFromJSON(serializedNode).setDiffType(serializedNode.diffType).setChangeId(serializedNode.changeId);
62
+ }
63
+ syncDOMState(element) {
64
+ element.contentEditable = "false";
65
+ element.dataset.diffType = this.__diffType;
66
+ if (this.__changeId) element.dataset.diffChangeId = this.__changeId;
67
+ else delete element.dataset.diffChangeId;
68
+ }
69
+ };
70
+ function $createTableCellDiffNode(diffType, changeId, headerState, colSpan, width) {
71
+ return $applyNodeReplacement(new TableCellDiffNode(diffType, changeId, headerState, colSpan, width));
72
+ }
73
+ function $isTableCellDiffNode(node) {
74
+ return node instanceof TableCellDiffNode;
75
+ }
76
+ //#endregion
77
+ export { $createTableCellDiffNode, $isTableCellDiffNode, TableCellDiffNode };
@@ -0,0 +1,73 @@
1
+ import { TableRowNode } from "@lexical/table";
2
+ import { $applyNodeReplacement } from "lexical";
3
+ //#region src/plugins/litexml/node/TableRowDiffNode.ts
4
+ /**
5
+ * A review-only table row. Keeping the diff state on a TableRowNode subclass
6
+ * preserves Lexical's table invariant: TableNode children are always rows.
7
+ */
8
+ var TableRowDiffNode = class TableRowDiffNode extends TableRowNode {
9
+ static getType() {
10
+ return "table-row-diff";
11
+ }
12
+ static clone(node) {
13
+ return new TableRowDiffNode(node.__diffType, node.__changeId, node.getHeight(), node.__key);
14
+ }
15
+ static importJSON(serializedNode) {
16
+ return $createTableRowDiffNode(serializedNode.diffType, serializedNode.changeId, serializedNode.height).updateFromJSON(serializedNode);
17
+ }
18
+ constructor(diffType, changeId, height, key) {
19
+ super(height, key);
20
+ this.__diffType = diffType;
21
+ this.__changeId = changeId;
22
+ }
23
+ createDOM(config) {
24
+ const element = super.createDOM(config);
25
+ const theme = config.theme.tableRowDiff;
26
+ if (typeof theme === "string" && theme) element.classList.add(theme);
27
+ this.syncDOMState(element);
28
+ return element;
29
+ }
30
+ exportJSON() {
31
+ return {
32
+ ...super.exportJSON(),
33
+ changeId: this.__changeId,
34
+ diffType: this.__diffType,
35
+ type: TableRowDiffNode.getType(),
36
+ version: 1
37
+ };
38
+ }
39
+ getChangeId() {
40
+ return this.getLatest().__changeId;
41
+ }
42
+ getDiffType() {
43
+ return this.getLatest().__diffType;
44
+ }
45
+ setChangeId(changeId) {
46
+ this.getWritable().__changeId = changeId;
47
+ return this;
48
+ }
49
+ setDiffType(diffType) {
50
+ this.getWritable().__diffType = diffType;
51
+ return this;
52
+ }
53
+ updateDOM(prevNode) {
54
+ return super.updateDOM(prevNode) || prevNode.__diffType !== this.__diffType || prevNode.__changeId !== this.__changeId;
55
+ }
56
+ updateFromJSON(serializedNode) {
57
+ return super.updateFromJSON(serializedNode).setDiffType(serializedNode.diffType).setChangeId(serializedNode.changeId);
58
+ }
59
+ syncDOMState(element) {
60
+ element.dataset.diffType = this.__diffType;
61
+ if (this.__changeId) element.dataset.diffChangeId = this.__changeId;
62
+ else delete element.dataset.diffChangeId;
63
+ element.contentEditable = "false";
64
+ }
65
+ };
66
+ function $createTableRowDiffNode(diffType, changeId, height) {
67
+ return $applyNodeReplacement(new TableRowDiffNode(diffType, changeId, height));
68
+ }
69
+ function $isTableRowDiffNode(node) {
70
+ return node instanceof TableRowDiffNode;
71
+ }
72
+ //#endregion
73
+ export { $createTableRowDiffNode, $isTableRowDiffNode, TableRowDiffNode };
@@ -13,6 +13,8 @@ interface LitexmlPluginOptions {
13
13
  * @default true
14
14
  */
15
15
  enabled?: boolean;
16
+ tableRowDiffTheme?: string;
17
+ tableCellDiffTheme?: string;
16
18
  theme?: string;
17
19
  }
18
20
  /**
@@ -1,10 +1,16 @@
1
1
  import { KernelPlugin, init_plugin } from "../../../editor-kernel/plugin.js";
2
+ import { $isDiffContentNode, DiffContentNode } from "../node/DiffContentNode.js";
2
3
  import { DiffNode } from "../node/DiffNode.js";
4
+ import { $isTableCellDiffNode, TableCellDiffNode } from "../node/TableCellDiffNode.js";
5
+ import { registerLegacyTableCellDiffNormalization } from "../table-cell-diff.js";
6
+ import { $isTableRowDiffNode, TableRowDiffNode } from "../node/TableRowDiffNode.js";
7
+ import { registerTableRowDiffNormalization } from "../table-row-diff.js";
3
8
  import { registerLiteXMLCommand } from "../command/index.js";
4
9
  import { registerLiteXMLDiffCommand } from "../command/diffCommand.js";
5
10
  import { ILitexmlService, LitexmlService } from "../service/litexml-service.js";
6
11
  import LitexmlDataSource from "../data-source/litexml-data-source.js";
7
12
  import { IMarkdownShortCutService } from "../../markdown/service/shortcut.js";
13
+ import { $isTableNode } from "@lexical/table";
8
14
  import { $nodesOfType, HISTORIC_TAG } from "lexical";
9
15
  //#region src/plugins/litexml/plugin/index.ts
10
16
  init_plugin();
@@ -20,14 +26,23 @@ const LitexmlPlugin = class extends KernelPlugin {
20
26
  super();
21
27
  this.kernel = kernel;
22
28
  this.config = config;
23
- kernel.registerThemes({ diffNode: config?.theme || "editor_diffNode" });
29
+ kernel.registerThemes({
30
+ diffNode: config?.theme || "editor_diffNode",
31
+ tableRowDiff: config?.tableRowDiffTheme || "editor_tableRowDiff",
32
+ tableCellDiff: config?.tableCellDiffTheme || "editor_tableCellDiff"
33
+ });
24
34
  const litexmlService = new LitexmlService();
25
35
  this.service = litexmlService;
26
36
  kernel.registerService(ILitexmlService, litexmlService);
27
37
  this.datasource = new LitexmlDataSource("litexml", (serviceId) => {
28
38
  return kernel.requireService(serviceId);
29
39
  }, litexmlService);
30
- kernel.registerNodes([DiffNode]);
40
+ kernel.registerNodes([
41
+ DiffNode,
42
+ DiffContentNode,
43
+ TableRowDiffNode,
44
+ TableCellDiffNode
45
+ ]);
31
46
  kernel.registerDecorator(DiffNode.getType(), {
32
47
  queryDOM: (el) => el.querySelector(".toolbar"),
33
48
  render: (node, editor) => {
@@ -39,6 +54,8 @@ const LitexmlPlugin = class extends KernelPlugin {
39
54
  onInit(editor) {
40
55
  this.register(registerLiteXMLCommand(editor, this.datasource));
41
56
  this.register(registerLiteXMLDiffCommand(editor));
57
+ this.register(registerLegacyTableCellDiffNormalization(editor));
58
+ this.register(registerTableRowDiffNormalization(editor));
42
59
  this.register(editor.registerNodeTransform(DiffNode, (node) => {
43
60
  if (node.diffType === "modify" && node.getChildrenSize() === 1) node.setDiffType("remove");
44
61
  }));
@@ -63,11 +80,14 @@ const LitexmlPlugin = class extends KernelPlugin {
63
80
  const diffNode = node;
64
81
  const lines = [];
65
82
  switch (diffNode.diffType) {
66
- case "modify":
67
- nodeToXML(diffNode.getChildAtIndex(1), lines, indent);
83
+ case "modify": {
84
+ const after = diffNode.getChildAtIndex(1);
85
+ if ($isDiffContentNode(after)) after.getChildren().forEach((child) => nodeToXML(child, lines, indent));
86
+ else nodeToXML(after, lines, indent);
68
87
  break;
88
+ }
69
89
  case "add":
70
- nodeToXML(diffNode.getChildAtIndex(0), lines, indent);
90
+ diffNode.getChildren().forEach((child) => nodeToXML(child, lines, indent));
71
91
  break;
72
92
  case "remove": break;
73
93
  case "listItemModify": diffNode.getChildAtIndex(1).getChildren().forEach((child) => {
@@ -76,16 +96,49 @@ const LitexmlPlugin = class extends KernelPlugin {
76
96
  }
77
97
  return { lines };
78
98
  });
99
+ this.service.registerXMLWriter(TableRowDiffNode.getType(), (node, ctx) => {
100
+ if (!$isTableRowDiffNode(node)) return false;
101
+ if (node.getDiffType() === "remove") return { lines: [] };
102
+ return ctx.createXmlNode("tr", {});
103
+ });
104
+ this.service.registerXMLWriter(TableCellDiffNode.getType(), (node, ctx) => {
105
+ if (!$isTableCellDiffNode(node)) return false;
106
+ if (node.getDiffType() === "remove") return { lines: [] };
107
+ const attributes = {};
108
+ if (node.getColSpan() > 1) attributes.colSpan = String(node.getColSpan());
109
+ if (node.getRowSpan() > 1) attributes.rowSpan = String(node.getRowSpan());
110
+ if (node.getBackgroundColor()) attributes.backgroundColor = node.getBackgroundColor();
111
+ return ctx.createXmlNode("td", attributes);
112
+ });
79
113
  }
80
114
  registerMarkdown() {
81
- this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownWriter(DiffNode.getType(), (ctx, node) => {
115
+ const markdownService = this.kernel.requireService(IMarkdownShortCutService);
116
+ markdownService?.registerMarkdownWriter(TableCellDiffNode.getType(), (ctx, node) => {
117
+ if (!$isTableCellDiffNode(node)) return false;
118
+ if (node.getDiffType() === "remove") return true;
119
+ if (!node.getNextSibling()) ctx.wrap("|", "|");
120
+ else ctx.wrap("|", "");
121
+ return true;
122
+ });
123
+ markdownService?.registerMarkdownWriter(TableRowDiffNode.getType(), (ctx, node) => {
124
+ if (!$isTableRowDiffNode(node)) return false;
125
+ if (node.getDiffType() === "remove") return true;
126
+ const parent = node.getParent();
127
+ if (!$isTableNode(parent)) return true;
128
+ if (node.getPreviousSiblings().some((sibling) => !$isTableRowDiffNode(sibling) || sibling.getDiffType() !== "remove")) ctx.wrap("", "\n");
129
+ else ctx.wrap("", `\n${parent.getColWidths()?.map(() => "|:--").join("")}|\n`);
130
+ });
131
+ markdownService?.registerMarkdownWriter(DiffNode.getType(), (ctx, node) => {
82
132
  const diffNode = node;
83
133
  switch (diffNode.diffType) {
84
- case "modify":
85
- ctx.processChild(ctx, diffNode.getChildAtIndex(1));
134
+ case "modify": {
135
+ const after = diffNode.getChildAtIndex(1);
136
+ if ($isDiffContentNode(after)) after.getChildren().forEach((child) => ctx.processChild(ctx, child));
137
+ else ctx.processChild(ctx, after);
86
138
  break;
139
+ }
87
140
  case "add":
88
- ctx.processChild(ctx, diffNode.getChildAtIndex(0));
141
+ diffNode.getChildren().forEach((child) => ctx.processChild(ctx, child));
89
142
  break;
90
143
  case "remove": break;
91
144
  case "listItemModify": diffNode.getChildAtIndex(1).getChildren().forEach((child) => {
@@ -10,6 +10,14 @@ import { useThemeMode } from "antd-style";
10
10
  const ReactDiffNodeToolbar = ({ editor, node }) => {
11
11
  const t = useTranslation();
12
12
  const { isDarkMode } = useThemeMode();
13
+ const handleActionPointerDown = (event, action) => {
14
+ event.preventDefault();
15
+ event.stopPropagation();
16
+ editor.dispatchCommand(LITEXML_DIFFNODE_COMMAND, {
17
+ action,
18
+ nodeKey: node.getKey()
19
+ });
20
+ };
13
21
  return /* @__PURE__ */ jsx(LexicalPortalContainer, {
14
22
  editor,
15
23
  node,
@@ -25,12 +33,7 @@ const ReactDiffNodeToolbar = ({ editor, node }) => {
25
33
  className: styles.reject,
26
34
  danger: true,
27
35
  icon: X,
28
- onClick: () => {
29
- editor.dispatchCommand(LITEXML_DIFFNODE_COMMAND, {
30
- action: 0,
31
- nodeKey: node.getKey()
32
- });
33
- },
36
+ onPointerDown: (event) => handleActionPointerDown(event, 0),
34
37
  size: {
35
38
  blockSize: 28,
36
39
  size: 16
@@ -40,12 +43,7 @@ const ReactDiffNodeToolbar = ({ editor, node }) => {
40
43
  "aria-label": "Accept change",
41
44
  className: styles.accept,
42
45
  icon: Check,
43
- onClick: () => {
44
- editor.dispatchCommand(LITEXML_DIFFNODE_COMMAND, {
45
- action: 1,
46
- nodeKey: node.getKey()
47
- });
48
- },
46
+ onPointerDown: (event) => handleActionPointerDown(event, 1),
49
47
  size: {
50
48
  blockSize: 28,
51
49
  size: 16