@lobehub/editor 4.29.1 → 5.0.0-beta.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 (149) hide show
  1. package/README.md +86 -0
  2. package/es/codemirror/components/CopyButton.js +1 -1
  3. package/es/codemirror/components/LanguageSelect.js +1 -3
  4. package/es/codemirror/components/MoreOptions.js +1 -2
  5. package/es/codemirror/components/Toolbar.js +1 -2
  6. package/es/codemirror/components/style.js +1 -1
  7. package/es/codemirror/style.js +1 -1
  8. package/es/codemirror/theme.js +1 -1
  9. package/es/editor-kernel/index.d.ts +2 -2
  10. package/es/editor-kernel/index.js +2 -2
  11. package/es/editor-kernel/react/useAnchor.js +1 -1
  12. package/es/headless/index.d.ts +5 -2
  13. package/es/headless/index.js +20 -22
  14. package/es/headless.d.ts +6 -507
  15. package/es/headless.js +903 -408
  16. package/es/i-node-identity-service-BBC_bg97.d.ts +522 -0
  17. package/es/index.d.ts +4 -2
  18. package/es/index.js +5 -2
  19. package/es/plugins/auto-complete/react/ReactAutoCompletePlugin.js +1 -1
  20. package/es/plugins/auto-complete/react/style.js +1 -1
  21. package/es/plugins/block/react/ReactBlockPlugin.js +2 -7
  22. package/es/plugins/block/react/style.js +1 -1
  23. package/es/plugins/code/react/CodeReactPlugin.js +1 -1
  24. package/es/plugins/code/react/style.js +1 -1
  25. package/es/plugins/codeblock/react/ReactCodeblockPlugin.js +1 -1
  26. package/es/plugins/codeblock/react/style.js +1 -1
  27. package/es/plugins/codemirror-block/react/CodemirrorNode.js +1 -3
  28. package/es/plugins/codemirror-block/react/MermaidPreview.js +1 -2
  29. package/es/plugins/collapsible/react/ReactCollapsiblePlugin.js +1 -1
  30. package/es/plugins/collapsible/react/style.js +74 -64
  31. package/es/plugins/common/data-source/json-data-source.js +33 -25
  32. package/es/plugins/common/data-source/text-data-source.js +6 -1
  33. package/es/plugins/common/index.d.ts +3 -1
  34. package/es/plugins/common/node/node-id.d.ts +7 -0
  35. package/es/plugins/common/node/node-id.js +484 -0
  36. package/es/plugins/common/node/node-identity-policy.d.ts +26 -0
  37. package/es/plugins/common/node/node-identity-policy.js +68 -0
  38. package/es/plugins/common/plugin/index.js +28 -2
  39. package/es/plugins/common/react/Placeholder/style.js +1 -1
  40. package/es/plugins/common/react/ReactPlainText.js +1 -1
  41. package/es/plugins/common/react/style.js +1 -1
  42. package/es/plugins/common/service/i-node-identity-service.d.ts +19 -0
  43. package/es/plugins/common/service/i-node-identity-service.js +4 -0
  44. package/es/plugins/common/service/node-identity-service.js +87 -0
  45. package/es/plugins/common/utils/index.js +3 -1
  46. package/es/plugins/file/react/components/ReactFile.js +1 -1
  47. package/es/plugins/file/react/style.js +1 -1
  48. package/es/plugins/hr/react/components/HRNode.js +1 -1
  49. package/es/plugins/hr/react/style.js +1 -1
  50. package/es/plugins/image/react/components/Image.js +1 -3
  51. package/es/plugins/image/react/components/ImageEditPopover.js +1 -3
  52. package/es/plugins/image/react/components/LazyImage.js +1 -1
  53. package/es/plugins/image/react/components/ResizeHandle.js +1 -1
  54. package/es/plugins/image/react/components/style.js +1 -1
  55. package/es/plugins/image/react/style.js +1 -1
  56. package/es/plugins/link/react/ReactLinkPlugin.js +1 -1
  57. package/es/plugins/link/react/components/LinkEdit.js +1 -3
  58. package/es/plugins/link/react/components/LinkToolbar.js +1 -1
  59. package/es/plugins/link/react/style.js +1 -1
  60. package/es/plugins/link-highlight/react/ReactLinkHighlightPlugin.js +1 -1
  61. package/es/plugins/link-highlight/react/style.js +1 -1
  62. package/es/plugins/list/plugin/index.js +7 -1
  63. package/es/plugins/list/react/style.js +1 -1
  64. package/es/plugins/litexml/command/index.d.ts +2 -2
  65. package/es/plugins/litexml/command/index.js +464 -292
  66. package/es/plugins/litexml/command/symbols.d.ts +18 -7
  67. package/es/plugins/litexml/command/symbols.js +2 -1
  68. package/es/plugins/litexml/data-source/litexml-data-source.js +34 -31
  69. package/es/plugins/litexml/diff-validation.js +68 -14
  70. package/es/plugins/litexml/index.d.ts +2 -2
  71. package/es/plugins/litexml/node/identity-policy.js +91 -0
  72. package/es/plugins/litexml/plugin/index.js +6 -0
  73. package/es/plugins/litexml/react/DiffNodeToolbar/index.js +1 -3
  74. package/es/plugins/litexml/react/DiffNodeToolbar/style.js +1 -1
  75. package/es/plugins/litexml/react/TableRowDiffToolbar.js +1 -3
  76. package/es/plugins/litexml/react/style.js +1 -1
  77. package/es/plugins/litexml/table-cell-diff.js +4 -0
  78. package/es/plugins/litexml/table-row-diff.js +4 -0
  79. package/es/plugins/litexml/utils/index.js +10 -23
  80. package/es/plugins/markdown/command/index.js +77 -4
  81. package/es/plugins/markdown/data-source/markdown-data-source.js +6 -1
  82. package/es/plugins/markdown/react/index.js +1 -1
  83. package/es/plugins/math/react/components/MathEditorContainer.js +1 -1
  84. package/es/plugins/math/react/components/MathEditorContent.js +1 -2
  85. package/es/plugins/math/react/components/Placeholder.js +1 -3
  86. package/es/plugins/math/react/style.js +1 -1
  87. package/es/plugins/mention/react/components/Mention.js +1 -1
  88. package/es/plugins/mention/react/style.js +1 -1
  89. package/es/plugins/slash/react/components/DefaultSlashMenu.js +1 -2
  90. package/es/plugins/slash/service/i-slash-service.d.ts +2 -2
  91. package/es/plugins/table/react/TableActionMenu/ActionMenu.js +1 -1
  92. package/es/plugins/table/react/TableActionMenu/index.js +1 -1
  93. package/es/plugins/table/react/TableActionMenu/style.js +1 -1
  94. package/es/plugins/table/react/TableColController.js +1 -1
  95. package/es/plugins/table/react/TableController/style.js +1 -1
  96. package/es/plugins/table/react/TableControllerButton.js +2 -2
  97. package/es/plugins/table/react/TableControllerMenu.js +2 -2
  98. package/es/plugins/table/react/TableHoverActions/index.js +1 -2
  99. package/es/plugins/table/react/TableHoverActions/style.js +1 -1
  100. package/es/plugins/table/react/TableResize/index.js +2 -2
  101. package/es/plugins/table/react/TableResize/style.js +1 -1
  102. package/es/plugins/table/react/TableRowController.js +1 -1
  103. package/es/plugins/table/react/index.js +1 -1
  104. package/es/plugins/table/react/style.js +1 -1
  105. package/es/plugins/toolbar/react/index.js +1 -2
  106. package/es/plugins/toolbar/react/style.js +1 -1
  107. package/es/plugins/yjs/plugin/index.js +5 -3
  108. package/es/plugins/yjs/plugin/utils/editor-state.js +8 -1
  109. package/es/plugins/yjs/plugin/utils/node-identity.js +106 -0
  110. package/es/plugins/yjs/service/index.js +38 -4
  111. package/es/react/ChatInput/ChatInput.js +1 -2
  112. package/es/react/ChatInput/style.js +1 -1
  113. package/es/react/ChatInput/type.d.ts +1 -1
  114. package/es/react/ChatInputActionBar/ChatInputActionBar.js +1 -2
  115. package/es/react/ChatInputActionBar/style.js +1 -1
  116. package/es/react/ChatInputActionBar/type.d.ts +1 -1
  117. package/es/react/ChatInputActions/ChatInputActions.js +1 -2
  118. package/es/react/ChatInputActions/components/ActionItem.js +1 -1
  119. package/es/react/ChatInputActions/components/ActionRender.js +1 -1
  120. package/es/react/ChatInputActions/components/CollapsedActions.js +1 -2
  121. package/es/react/ChatInputActions/style.js +1 -1
  122. package/es/react/ChatInputActions/type.d.ts +4 -4
  123. package/es/react/CodeLanguageSelect/CodeLanguageSelect.js +1 -3
  124. package/es/react/CodeLanguageSelect/style.js +1 -1
  125. package/es/react/CodeLanguageSelect/type.d.ts +1 -1
  126. package/es/react/FloatActions/FloatActions.js +1 -2
  127. package/es/react/FloatActions/components/ActionItem.js +1 -1
  128. package/es/react/FloatActions/components/ActionRender.js +1 -1
  129. package/es/react/FloatActions/components/CollapsedActions.js +1 -2
  130. package/es/react/FloatActions/style.js +1 -1
  131. package/es/react/FloatActions/type.d.ts +4 -4
  132. package/es/react/SendButton/SendButton.js +1 -2
  133. package/es/react/SendButton/components/SendIcon.js +1 -1
  134. package/es/react/SendButton/components/StopIcon.js +1 -1
  135. package/es/react/SendButton/style.js +1 -1
  136. package/es/react/SendButton/type.d.ts +1 -1
  137. package/es/renderer/LexicalDiff.js +1 -1
  138. package/es/renderer/LexicalRenderer.js +1 -1
  139. package/es/renderer/diff/compute.js +2 -2
  140. package/es/renderer/diff/style.js +1 -1
  141. package/es/renderer/engine/render-builtin-node.js +2 -2
  142. package/es/renderer/renderers/codeblock.js +1 -1
  143. package/es/renderer/renderers/horizontalrule.js +1 -1
  144. package/es/renderer/renderers/mermaid.js +1 -1
  145. package/es/renderer/style.js +1 -1
  146. package/es/{symbols-Do7jD4Tt.d.ts → symbols-Dyr9yyS0.d.ts} +18 -7
  147. package/package.json +6 -8
  148. package/scripts/postinstall-lexical-patch.cjs +0 -21
  149. package/patches/lexical@0.42.0.patch +0 -82
@@ -1,18 +1,22 @@
1
1
  import { createDebugLogger, init_debug } from "../../../utils/debug.js";
2
- import { $closest, getKernelFromEditor, init_utils } from "../../../editor-kernel/utils.js";
3
- import { findNewIllegalDiffPaths, projectLiteXmlOperation } from "../diff-validation.js";
2
+ import { $ensureUniqueNodeIds, $findNodeById, $getNodeId, init_node_id } from "../../common/node/node-id.js";
3
+ import { exportNodeToJSON, init_utils } from "../../common/utils/index.js";
4
+ import { $closest, init_utils as init_utils$1 } from "../../../editor-kernel/utils.js";
5
+ import { findNewIllegalDiffPaths, hasActiveLiteXmlNodeId, projectLiteXmlOperation } from "../diff-validation.js";
4
6
  import { $createDiffContentNode, $isDiffContentNode } from "../node/DiffContentNode.js";
5
7
  import { $createDiffNode, $isDiffNode, DiffNode } from "../node/DiffNode.js";
6
8
  import { $isTableCellDiffNode } from "../node/TableCellDiffNode.js";
7
- import { $cloneNode, $parseSerializedNodeImpl, charToId } from "../utils/index.js";
9
+ import { $cloneNode, $parseSerializedNodeImpl } from "../utils/index.js";
8
10
  import { $createTableCellDiffFromCell, $getLogicalRowWidth, $getTableCellColumnIndex, $getTableForCell, $shrinkTableWidthsAfterCellRemoval, $updateTableWidthsForCellInsertion } from "../table-cell-diff.js";
9
11
  import { $areTableRowStructuresCompatible, $createTableRowDiffFromRow } from "../table-row-diff.js";
10
- import { LITEXML_APPLY_COMMAND, LITEXML_INSERT_COMMAND, LITEXML_MODIFY_COMMAND, LITEXML_REMOVE_COMMAND } from "./symbols.js";
12
+ import { LITEXML_APPLY_COMMAND, LITEXML_INSERT_COMMAND, LITEXML_MODIFY_COMMAND, LITEXML_MODIFY_WITH_RESULTS_COMMAND, LITEXML_REMOVE_COMMAND } from "./symbols.js";
11
13
  import { $isTableCellNode, $isTableNode, $isTableRowNode } from "@lexical/table";
12
14
  import { $createParagraphNode, $getNodeByKey, $getRoot, $insertNodes, $isElementNode, COMMAND_PRIORITY_EDITOR } from "lexical";
13
15
  import { mergeRegister } from "@lexical/utils";
14
- import { $isListItemNode } from "@lexical/list";
16
+ import { $isListItemNode, $isListNode } from "@lexical/list";
15
17
  //#region src/plugins/litexml/command/index.ts
18
+ init_utils$1();
19
+ init_node_id();
16
20
  init_utils();
17
21
  init_debug();
18
22
  const logger = createDebugLogger("plugin", "litexml");
@@ -22,9 +26,9 @@ function toArrayXml(litexml) {
22
26
  function hasNewIllegalDiffs(previous, projected) {
23
27
  return findNewIllegalDiffPaths(previous, projected);
24
28
  }
25
- function projectOperation(dataSource, document, operation) {
29
+ function projectOperation(dataSource, document, operation, readXml = (xml) => dataSource.readLiteXMLToInode(xml)) {
26
30
  try {
27
- const projected = projectLiteXmlOperation(document, operation, (xml) => dataSource.readLiteXMLToInode(xml));
31
+ const projected = projectLiteXmlOperation(document, operation, readXml);
28
32
  const newIllegalDiffs = hasNewIllegalDiffs(document, projected);
29
33
  if (newIllegalDiffs.length > 0) {
30
34
  logger.warn("⚠️ Skipping operation with illegal nested diff", newIllegalDiffs);
@@ -37,19 +41,163 @@ function projectOperation(dataSource, document, operation) {
37
41
  }
38
42
  }
39
43
  function toProjectionOperation(operation) {
40
- if (operation.action === "remove") return {
41
- ...operation,
42
- id: charToId(operation.id)
43
- };
44
- if (operation.action === "insert") return {
45
- ...operation,
46
- ..."beforeId" in operation ? { beforeId: operation.beforeId === "root" ? "root" : charToId(operation.beforeId) } : { afterId: operation.afterId === "root" ? "root" : charToId(operation.afterId) }
47
- };
48
44
  return operation;
49
45
  }
46
+ function getSerializedNodeId(node) {
47
+ if (typeof node?.id === "string" && node.id.length > 0) return node.id;
48
+ const nodeId = node?.$?.properties?.nodeId;
49
+ return typeof nodeId === "string" && nodeId.length > 0 ? nodeId : void 0;
50
+ }
51
+ function getActiveSerializedDocument() {
52
+ return { root: exportNodeToJSON($getRoot()) };
53
+ }
54
+ /** Resolve every target before staging any part of one modify operation. */
55
+ function prepareModify(editor, dataSource, xmls) {
56
+ const pairs = [];
57
+ const parsed = /* @__PURE__ */ new Map();
58
+ const targetIds = /* @__PURE__ */ new Set();
59
+ for (const xml of xmls) {
60
+ const inode = dataSource.readLiteXMLToInode(xml);
61
+ parsed.set(xml, inode);
62
+ const children = inode.root?.children;
63
+ if (!children?.length) throw new Error("Modify operation contains no LiteXML nodes.");
64
+ for (const child of children) {
65
+ const nodeId = getSerializedNodeId(child);
66
+ if (!nodeId) throw new Error("Modify operation root is missing its public node id.");
67
+ if (targetIds.has(nodeId)) throw new Error(`Node id "${nodeId}" is targeted more than once.`);
68
+ targetIds.add(nodeId);
69
+ const oldNode = $findNodeById(nodeId);
70
+ if (!oldNode) throw new Error(`Node id "${nodeId}" was not found.`);
71
+ const newNode = $parseSerializedNodeImpl(child, editor);
72
+ if (oldNode.isInline() !== newNode.isInline()) throw new Error(`Node id "${nodeId}" cannot change between inline and block structure.`);
73
+ const parent = oldNode.getParent();
74
+ if (($isListNode(parent) || $isListItemNode(oldNode) || $isListItemNode(newNode)) && (!$isListNode(parent) || !$isListItemNode(oldNode) || !$isListItemNode(newNode))) throw new Error(`Node id "${nodeId}" has an incompatible list-item structure.`);
75
+ if ($isTableNode(parent) && (!$isTableRowNode(oldNode) || !$isTableRowNode(newNode)) || $isTableRowNode(parent) && (!$isTableCellNode(oldNode) || !$isTableCellNode(newNode)) || $isTableRowNode(newNode) && !$isTableNode(parent) || $isTableCellNode(newNode) && !$isTableRowNode(parent)) throw new Error(`Node id "${nodeId}" has an incompatible table structure.`);
76
+ if (($isTableRowNode(oldNode) || $isTableRowNode(newNode)) && (!$isTableRowNode(oldNode) || !$isTableRowNode(newNode) || !$isTableNode(parent) || !$areTableRowStructuresCompatible(oldNode, newNode))) throw new Error(`Node id "${nodeId}" has an incompatible table row structure.`);
77
+ pairs.push({
78
+ newNode,
79
+ oldNode
80
+ });
81
+ }
82
+ }
83
+ const targetKeys = new Set(pairs.map(({ oldNode }) => oldNode.getKey()));
84
+ for (const { oldNode } of pairs) for (let parent = oldNode.getParent(); parent; parent = parent.getParent()) if (targetKeys.has(parent.getKey())) throw new Error("Modify operation targets overlapping ancestor and descendant nodes.");
85
+ return {
86
+ pairs,
87
+ readXml: (xml) => parsed.get(xml)
88
+ };
89
+ }
90
+ function getBatchTargetError(document, operation, dataSource) {
91
+ if (operation.action === "remove") return hasActiveLiteXmlNodeId(document.root, operation.id) ? void 0 : `Node id "${operation.id}" was not found.`;
92
+ if (operation.action === "insert") {
93
+ try {
94
+ if (!dataSource.readLiteXMLToInode(operation.litexml).root.children?.length) return "Insert operation contains no LiteXML nodes.";
95
+ } catch (error) {
96
+ return error instanceof Error ? error.message : String(error);
97
+ }
98
+ const anchorId = "beforeId" in operation ? operation.beforeId : operation.afterId;
99
+ if (anchorId === "root") return (document.root.children?.length || 0) > 0 ? void 0 : "The document has no node to use as an insertion anchor.";
100
+ return hasActiveLiteXmlNodeId(document.root, anchorId) ? void 0 : `Insertion anchor "${anchorId}" was not found.`;
101
+ }
102
+ }
103
+ function applyLiteXMLBatch(editor, dataSource, operations) {
104
+ let projectedDocument = getActiveSerializedDocument();
105
+ const afterAnchors = /* @__PURE__ */ new Map();
106
+ const results = [];
107
+ operations.forEach((operation, index) => {
108
+ const targetError = getBatchTargetError(projectedDocument, operation, dataSource);
109
+ if (targetError) {
110
+ results.push({
111
+ action: operation.action,
112
+ index,
113
+ reason: targetError,
114
+ status: "failed"
115
+ });
116
+ return;
117
+ }
118
+ let preparedModify;
119
+ if (operation.action === "modify") try {
120
+ preparedModify = prepareModify(editor, dataSource, toArrayXml(operation.litexml));
121
+ } catch (error) {
122
+ results.push({
123
+ action: operation.action,
124
+ index,
125
+ reason: error instanceof Error ? error.message : String(error),
126
+ status: "failed"
127
+ });
128
+ return;
129
+ }
130
+ if (!projectOperation(dataSource, projectedDocument, toProjectionOperation(operation), preparedModify?.readXml)) {
131
+ results.push({
132
+ action: operation.action,
133
+ index,
134
+ reason: "The operation would create an invalid nested review diff.",
135
+ status: "failed"
136
+ });
137
+ return;
138
+ }
139
+ try {
140
+ let applied = false;
141
+ switch (operation.action) {
142
+ case "modify":
143
+ applied = applyPreparedModify(editor, preparedModify);
144
+ break;
145
+ case "remove":
146
+ applied = handleRemove(editor, operation.id, true);
147
+ break;
148
+ case "insert": {
149
+ const cursorIds = "afterId" in operation ? afterAnchors.get(operation.afterId) : void 0;
150
+ let override;
151
+ if (cursorIds) for (let cursorIndex = cursorIds.length - 1; cursorIndex >= 0; cursorIndex--) {
152
+ const cursor = $findNodeById(cursorIds[cursorIndex]);
153
+ if (cursor) {
154
+ override = cursor;
155
+ break;
156
+ }
157
+ }
158
+ const insertedNodeId = handleInsert(editor, {
159
+ ...operation,
160
+ delay: true
161
+ }, dataSource, override);
162
+ applied = insertedNodeId !== null;
163
+ if ("afterId" in operation && insertedNodeId) {
164
+ const nextCursors = afterAnchors.get(operation.afterId) || [];
165
+ nextCursors.push(insertedNodeId);
166
+ afterAnchors.set(operation.afterId, nextCursors);
167
+ }
168
+ break;
169
+ }
170
+ }
171
+ if (!applied) {
172
+ results.push({
173
+ action: operation.action,
174
+ index,
175
+ reason: "The editor could not apply the operation.",
176
+ status: "failed"
177
+ });
178
+ return;
179
+ }
180
+ projectedDocument = getActiveSerializedDocument();
181
+ results.push({
182
+ action: operation.action,
183
+ index,
184
+ status: "applied"
185
+ });
186
+ } catch (error) {
187
+ results.push({
188
+ action: operation.action,
189
+ index,
190
+ reason: error instanceof Error ? error.message : String(error),
191
+ status: "failed"
192
+ });
193
+ }
194
+ });
195
+ return results;
196
+ }
50
197
  function tryParseChild(child, editor) {
51
198
  try {
52
- const oldNode = $getNodeByKey(child.id);
199
+ const nodeId = typeof child.id === "string" ? child.id : child.$?.properties?.nodeId;
200
+ const oldNode = typeof nodeId === "string" ? $findNodeById(nodeId) : null;
53
201
  return {
54
202
  newNode: $parseSerializedNodeImpl(child, editor),
55
203
  oldNode
@@ -66,14 +214,14 @@ function handleReplaceForApplyDelay(oldNode, newNode, modifyBlockNodes, diffNode
66
214
  if ($isTableRowNode(oldNode) || $isTableRowNode(newNode)) {
67
215
  if (!$isTableRowNode(oldNode) || !$isTableRowNode(newNode) || !$isTableNode(oldNode.getParent()) || !$areTableRowStructuresCompatible(oldNode, newNode)) {
68
216
  logger.error(`❌ Invalid table row modification for row ${oldNode.getKey()}.`);
69
- return;
217
+ return false;
70
218
  }
71
219
  const changeId = `${oldNode.getKey()}:${newNode.getKey()}`;
72
220
  const removeRow = $createTableRowDiffFromRow(editor, oldNode, "remove", changeId);
73
221
  const addRow = $createTableRowDiffFromRow(editor, newNode, "add", changeId);
74
222
  oldNode.replace(removeRow, false);
75
223
  removeRow.insertAfter(addRow);
76
- return;
224
+ return true;
77
225
  }
78
226
  if ($isTableCellNode(oldNode) && $isTableCellNode(newNode)) {
79
227
  const existingDiff = oldNode.getChildren().find($isDiffNode);
@@ -83,7 +231,7 @@ function handleReplaceForApplyDelay(oldNode, newNode, modifyBlockNodes, diffNode
83
231
  newNode.getChildren().forEach((child) => {
84
232
  existingDiff.append($cloneNode(child, editor));
85
233
  });
86
- return;
234
+ return true;
87
235
  }
88
236
  const after = existingDiff.getChildren().find((child) => $isDiffContentNode(child) && child.side === "after");
89
237
  if (existingDiff.diffType === "modify" && after && $isDiffContentNode(after)) {
@@ -91,7 +239,7 @@ function handleReplaceForApplyDelay(oldNode, newNode, modifyBlockNodes, diffNode
91
239
  newNode.getChildren().forEach((child) => {
92
240
  after.append($cloneNode(child, editor));
93
241
  });
94
- return;
242
+ return true;
95
243
  }
96
244
  }
97
245
  const before = $createDiffContentNode("before");
@@ -102,27 +250,42 @@ function handleReplaceForApplyDelay(oldNode, newNode, modifyBlockNodes, diffNode
102
250
  diffNode.append(before, after);
103
251
  oldNode.clear();
104
252
  oldNode.append(diffNode);
105
- return;
253
+ return true;
106
254
  }
107
255
  const oldBlock = $closest(oldNode, (node) => node.isInline() === false);
108
256
  if (!oldBlock) throw new Error("Old block node not found for diffing.");
109
257
  if ($closest(oldNode, (node) => node.getType() === DiffNode.getType())) {
110
258
  oldNode.replace(newNode, false);
111
- return;
259
+ return true;
112
260
  }
113
261
  if ($isListItemNode(oldNode) && $isListItemNode(newNode)) {
262
+ const existingListDiff = oldNode.getChildren().find($isDiffNode);
263
+ if (existingListDiff?.diffType === "listItemModify") {
264
+ const after = existingListDiff.getChildAtIndex(1);
265
+ if ($isElementNode(after)) {
266
+ after.clear();
267
+ newNode.getChildren().forEach((child) => after.append($cloneNode(child, editor)));
268
+ return true;
269
+ }
270
+ }
271
+ if (existingListDiff?.diffType === "listItemAdd") {
272
+ existingListDiff.clear();
273
+ newNode.getChildren().forEach((child) => existingListDiff.append($cloneNode(child, editor)));
274
+ return true;
275
+ }
114
276
  const before = $createParagraphNode();
115
277
  const after = $createParagraphNode();
116
278
  oldNode.getChildren().forEach((child) => before.append(child));
117
279
  newNode.getChildren().forEach((child) => after.append(child));
118
280
  oldNode.clear();
119
281
  oldNode.append($createDiffNode("listItemModify").append(before, after));
120
- return;
282
+ return true;
121
283
  }
122
284
  if (oldNode === oldBlock) {
123
285
  const diffNode = $createDiffNode("modify");
124
286
  oldNode.replace(diffNode, false);
125
287
  diffNode.append($createDiffContentNode("before").append(oldBlock), $createDiffContentNode("after").append(newNode));
288
+ return true;
126
289
  } else {
127
290
  if (!modifyBlockNodes.has(oldBlock.getKey())) {
128
291
  modifyBlockNodes.add(oldBlock.getKey());
@@ -131,6 +294,7 @@ function handleReplaceForApplyDelay(oldNode, newNode, modifyBlockNodes, diffNode
131
294
  diffNodeMap.set(oldBlock.getKey(), diffNode);
132
295
  }
133
296
  oldNode.replace(newNode, false);
297
+ return true;
134
298
  }
135
299
  }
136
300
  function finalizeModifyBlocks(modifyBlockNodes, diffNodeMap, editor) {
@@ -151,6 +315,16 @@ function finalizeModifyBlocks(modifyBlockNodes, diffNodeMap, editor) {
151
315
  newDiffNode.append(p);
152
316
  blockNode.append(newDiffNode);
153
317
  continue;
318
+ } else if ($isTableCellNode(blockNode) && $isTableRowNode(blockNode.getParent()) && $isTableCellNode(diffNode.getFirstChild())) {
319
+ const beforeCell = diffNode.getFirstChild();
320
+ if (!$isTableCellNode(beforeCell)) continue;
321
+ const before = $createDiffContentNode("before");
322
+ const after = $createDiffContentNode("after");
323
+ beforeCell.getChildren().forEach((child) => before.append(child));
324
+ blockNode.getChildren().forEach((child) => after.append($cloneNode(child, editor)));
325
+ blockNode.clear();
326
+ blockNode.append($createDiffNode("modify").append(before, after));
327
+ continue;
154
328
  } else {
155
329
  diffNode.append($cloneNode(blockNode, editor));
156
330
  blockNode.replace(diffNode, false);
@@ -181,6 +355,18 @@ function wrapBlockModify(oldBlock, editor, changeFn) {
181
355
  oldBlock.append(diffNode);
182
356
  return;
183
357
  }
358
+ if ($isTableCellNode(oldBlock) && !$isTableCellDiffNode(oldBlock) && $isTableRowNode(oldBlock.getParent())) {
359
+ const before = $createDiffContentNode("before");
360
+ oldBlock.getChildren().forEach((child) => before.append($cloneNode(child, editor)));
361
+ changeFn();
362
+ const newBlock = $getNodeByKey(oldBlock.getKey());
363
+ if (!$isTableCellNode(newBlock)) throw new Error("Updated table cell node not found for modify wrapper.");
364
+ const after = $createDiffContentNode("after");
365
+ newBlock.getChildren().forEach((child) => after.append($cloneNode(child, editor)));
366
+ newBlock.clear();
367
+ newBlock.append($createDiffNode("modify").append(before, after));
368
+ return;
369
+ }
184
370
  const diffNode = $createDiffNode("modify");
185
371
  diffNode.append($cloneNode(oldBlock, editor));
186
372
  changeFn();
@@ -190,345 +376,331 @@ function wrapBlockModify(oldBlock, editor, changeFn) {
190
376
  newBlock.replace(diffNode, false);
191
377
  }
192
378
  function registerLiteXMLCommand(editor, dataSource) {
193
- return mergeRegister(editor.registerCommand(LITEXML_MODIFY_COMMAND, (payload) => {
194
- const resultPayload = payload.reduce((acc, cur) => {
195
- if (cur.action === "insert") acc.unshift(cur);
196
- else acc.push(cur);
197
- return acc;
198
- }, []);
199
- let projectedDocument = getKernelFromEditor(editor).getDocument("json");
200
- const safePayload = resultPayload.filter((item) => {
201
- const nextProjection = projectOperation(dataSource, projectedDocument, toProjectionOperation(item));
202
- if (!nextProjection) return false;
203
- projectedDocument = nextProjection;
204
- return true;
205
- });
206
- try {
207
- safePayload.forEach((item) => {
208
- const { action } = item;
209
- switch (action) {
210
- case "modify": {
211
- const { litexml } = item;
212
- handleModify(editor, dataSource, toArrayXml(litexml), true);
213
- break;
214
- }
215
- case "remove": {
216
- const { id } = item;
217
- handleRemove(editor, charToId(id), true);
218
- break;
219
- }
220
- case "insert":
221
- handleInsert(editor, {
222
- ...item,
223
- delay: true
224
- }, dataSource);
225
- break;
226
- default: logger.warn(`⚠️ Unknown action type: ${action}`);
227
- }
228
- });
229
- return false;
230
- } catch (error) {
231
- logger.error("❌ Error processing LITEXML_MODIFY_COMMAND:", error);
232
- return false;
233
- }
379
+ return mergeRegister(editor.registerCommand(LITEXML_MODIFY_COMMAND, (operations) => {
380
+ applyLiteXMLBatch(editor, dataSource, operations);
381
+ return false;
382
+ }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(LITEXML_MODIFY_WITH_RESULTS_COMMAND, ({ operations, onResults }) => {
383
+ onResults(applyLiteXMLBatch(editor, dataSource, operations));
384
+ return true;
234
385
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(LITEXML_APPLY_COMMAND, (payload) => {
235
386
  const { litexml, delay } = payload;
236
387
  const arrayXml = toArrayXml(litexml);
237
388
  if (!delay) {
238
- handleModify(editor, dataSource, arrayXml, delay);
389
+ handleModify(editor, dataSource, arrayXml);
239
390
  return false;
240
391
  }
241
392
  const operation = {
242
393
  action: "modify",
243
394
  litexml
244
395
  };
245
- if (projectOperation(dataSource, getKernelFromEditor(editor).getDocument("json"), toProjectionOperation(operation))) handleModify(editor, dataSource, arrayXml, delay);
396
+ const document = getActiveSerializedDocument();
397
+ try {
398
+ const prepared = prepareModify(editor, dataSource, arrayXml);
399
+ if (projectOperation(dataSource, document, toProjectionOperation(operation), prepared.readXml)) applyPreparedModify(editor, prepared);
400
+ } catch (error) {
401
+ logger.error("❌ Failed to apply LiteXML modification:", error);
402
+ }
246
403
  return false;
247
404
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(LITEXML_REMOVE_COMMAND, (payload) => {
248
405
  const { id, delay } = payload;
249
- const key = charToId(id);
250
406
  if (!delay) {
251
- handleRemove(editor, key, delay);
407
+ handleRemove(editor, id, delay);
252
408
  return false;
253
409
  }
254
410
  const operation = {
255
411
  action: "remove",
256
412
  id
257
413
  };
258
- if (projectOperation(dataSource, getKernelFromEditor(editor).getDocument("json"), toProjectionOperation(operation))) handleRemove(editor, key, delay);
414
+ if (projectOperation(dataSource, getActiveSerializedDocument(), toProjectionOperation(operation))) handleRemove(editor, id, delay);
259
415
  return false;
260
416
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(LITEXML_INSERT_COMMAND, (payload) => {
261
417
  if (!payload.delay) {
262
418
  handleInsert(editor, payload, dataSource);
263
419
  return false;
264
420
  }
265
- if (projectOperation(dataSource, getKernelFromEditor(editor).getDocument("json"), toProjectionOperation({
421
+ if (projectOperation(dataSource, getActiveSerializedDocument(), toProjectionOperation({
266
422
  action: "insert",
267
423
  ...payload
268
424
  }))) handleInsert(editor, payload, dataSource);
269
425
  return false;
270
426
  }, COMMAND_PRIORITY_EDITOR));
271
427
  }
272
- function handleModify(editor, dataSource, arrayXml, delay) {
273
- if (delay) editor.update(() => {
274
- const modifyBlockNodes = /* @__PURE__ */ new Set();
275
- const diffNodeMap = /* @__PURE__ */ new Map();
276
- arrayXml.forEach((xml) => {
277
- dataSource.readLiteXMLToInode(xml).root.children.forEach((child) => {
278
- try {
279
- const { oldNode, newNode } = tryParseChild(child, editor);
280
- if (oldNode && newNode) handleReplaceForApplyDelay(oldNode, newNode, modifyBlockNodes, diffNodeMap, editor);
281
- else logger.warn(`⚠️ Node with key ${child.id} not found for diffing.`);
282
- } catch (error) {
283
- logger.error("❌ Error replacing node:", error);
284
- }
285
- });
286
- });
287
- finalizeModifyBlocks(modifyBlockNodes, diffNodeMap, editor);
288
- });
289
- else editor.update(() => {
290
- arrayXml.forEach((xml) => {
291
- const inode = dataSource.readLiteXMLToInode(xml);
292
- let prevNode = null;
293
- inode.root.children.forEach((child) => {
294
- try {
295
- const { oldNode, newNode } = tryParseChild(child, editor);
296
- if (oldNode && newNode) prevNode = oldNode.replace(newNode, false);
297
- else if (newNode) {
298
- if (prevNode) {
299
- if (!newNode.isInline()) {
300
- const prevBlock = $closest(prevNode, (node) => node.isInline() === false);
301
- if (prevBlock) prevNode = prevBlock.insertAfter(newNode);
302
- else {
303
- $insertNodes([newNode]);
304
- prevNode = newNode;
305
- }
306
- } else prevNode = prevNode.insertAfter(newNode);
307
- } else $insertNodes([newNode]);
428
+ function applyPreparedModify(editor, prepared) {
429
+ const modifyBlockNodes = /* @__PURE__ */ new Set();
430
+ const diffNodeMap = /* @__PURE__ */ new Map();
431
+ for (const { oldNode, newNode } of prepared.pairs) if (!handleReplaceForApplyDelay(oldNode, newNode, modifyBlockNodes, diffNodeMap, editor)) throw new Error(`Node id "${$getNodeId(oldNode)}" could not be modified.`);
432
+ finalizeModifyBlocks(modifyBlockNodes, diffNodeMap, editor);
433
+ return prepared.pairs.length > 0;
434
+ }
435
+ function handleModify(editor, dataSource, arrayXml) {
436
+ let applied = false;
437
+ arrayXml.forEach((xml) => {
438
+ const inode = dataSource.readLiteXMLToInode(xml);
439
+ let prevNode = null;
440
+ inode.root.children.forEach((child) => {
441
+ try {
442
+ const { oldNode, newNode } = tryParseChild(child, editor);
443
+ if (oldNode && newNode) {
444
+ prevNode = oldNode.replace(newNode, false);
445
+ applied = true;
446
+ } else if (newNode) {
447
+ if (prevNode) {
448
+ if (!newNode.isInline()) {
449
+ const prevBlock = $closest(prevNode, (node) => node.isInline() === false);
450
+ if (prevBlock) prevNode = prevBlock.insertAfter(newNode);
451
+ else {
452
+ $insertNodes([newNode]);
453
+ prevNode = newNode;
454
+ }
455
+ applied = true;
456
+ } else {
457
+ prevNode = prevNode.insertAfter(newNode);
458
+ applied = true;
459
+ }
460
+ } else {
461
+ $insertNodes([newNode]);
462
+ applied = true;
308
463
  }
309
- } catch (error) {
310
- logger.error("❌ Error replacing node:", error);
311
464
  }
312
- });
465
+ } catch (error) {
466
+ logger.error("❌ Error replacing node:", error);
467
+ }
313
468
  });
314
469
  });
470
+ return applied;
315
471
  }
316
- function handleRemove(editor, key, delay) {
317
- editor.update(() => {
318
- const node = $getNodeByKey(key);
319
- if (!node) return;
320
- if (!delay) {
321
- if ($isTableCellNode(node)) {
322
- const table = $getTableForCell(node);
323
- const columnIndex = $getTableCellColumnIndex(node);
324
- const span = node.getColSpan();
325
- node.remove();
326
- if (table && columnIndex >= 0) $shrinkTableWidthsAfterCellRemoval(table, columnIndex, span);
327
- return;
328
- }
329
- node.remove();
330
- return;
331
- }
472
+ function handleRemove(editor, nodeId, delay) {
473
+ let applied = false;
474
+ const node = $findNodeById(nodeId);
475
+ if (!node) return false;
476
+ if (!delay) {
332
477
  if ($isTableCellNode(node)) {
333
478
  const table = $getTableForCell(node);
334
479
  const columnIndex = $getTableCellColumnIndex(node);
335
- if (!table || columnIndex < 0) {
336
- logger.error(`❌ Table cell ${node.getKey()} is not attached to a valid table row.`);
337
- return;
338
- }
339
- const changeId = `${table.getKey()}:column:${columnIndex}`;
340
- node.replace($createTableCellDiffFromCell(editor, node, "remove", changeId), false);
341
- return;
480
+ const span = node.getColSpan();
481
+ node.remove();
482
+ applied = true;
483
+ if (table && columnIndex >= 0) $shrinkTableWidthsAfterCellRemoval(table, columnIndex, span);
484
+ return true;
342
485
  }
343
- if ($isTableRowNode(node) && $isTableNode(node.getParent())) {
344
- node.replace($createTableRowDiffFromRow(editor, node, "remove"), false);
345
- return;
486
+ node.remove();
487
+ applied = true;
488
+ return true;
489
+ }
490
+ if ($isTableCellNode(node)) {
491
+ const table = $getTableForCell(node);
492
+ const columnIndex = $getTableCellColumnIndex(node);
493
+ if (!table || columnIndex < 0) {
494
+ logger.error(`❌ Table cell ${node.getKey()} is not attached to a valid table row.`);
495
+ return false;
346
496
  }
347
- if (node.isInline() === false) {
348
- const originDiffNode = $closest(node, (node) => node.getType() === DiffNode.getType());
349
- if (originDiffNode) {
350
- switch (originDiffNode.diffType) {
351
- case "add":
352
- originDiffNode.remove();
353
- return;
354
- case "modify": {
355
- const children = originDiffNode.getChildren();
356
- const newDiff = $createDiffNode("remove");
357
- const before = children[0];
358
- newDiff.append(...$isDiffContentNode(before) ? before.getChildren() : [before]);
359
- originDiffNode.replace(newDiff, false);
360
- return;
361
- }
362
- case "listItemModify": {
363
- const children = originDiffNode.getChildren();
364
- originDiffNode.replace(children[0], false).selectEnd();
365
- return;
366
- }
497
+ const changeId = `${table.getKey()}:column:${columnIndex}`;
498
+ node.replace($createTableCellDiffFromCell(editor, node, "remove", changeId), false);
499
+ applied = true;
500
+ return true;
501
+ }
502
+ if ($isTableRowNode(node) && $isTableNode(node.getParent())) {
503
+ node.replace($createTableRowDiffFromRow(editor, node, "remove"), false);
504
+ applied = true;
505
+ return true;
506
+ }
507
+ if (node.isInline() === false) {
508
+ const originDiffNode = $closest(node, (node) => node.getType() === DiffNode.getType());
509
+ if (originDiffNode) {
510
+ switch (originDiffNode.diffType) {
511
+ case "add":
512
+ originDiffNode.remove();
513
+ applied = true;
514
+ return true;
515
+ case "modify": {
516
+ const children = originDiffNode.getChildren();
517
+ const newDiff = $createDiffNode("remove");
518
+ const before = children[0];
519
+ newDiff.append(...$isDiffContentNode(before) ? before.getChildren() : [before]);
520
+ originDiffNode.replace(newDiff, false);
521
+ applied = true;
522
+ return true;
523
+ }
524
+ case "listItemModify": {
525
+ const children = originDiffNode.getChildren();
526
+ originDiffNode.replace(children[0], false).selectEnd();
527
+ applied = true;
528
+ return true;
367
529
  }
368
- return;
369
- }
370
- if ($isListItemNode(node)) {
371
- const diffNode = $createDiffNode("listItemRemove");
372
- node.getChildren().forEach((child) => {
373
- diffNode.append($cloneNode(child, editor));
374
- });
375
- node.clear();
376
- node.append(diffNode);
377
- } else {
378
- const diffNode = $createDiffNode("remove");
379
- diffNode.append($cloneNode(node, editor));
380
- node.replace(diffNode, false);
381
- }
382
- } else {
383
- const oldBlock = $closest(node, (node) => node.isInline() === false);
384
- if (!oldBlock) throw new Error("Old block node not found for removal.");
385
- if ($closest(node, (node) => node.getType() === DiffNode.getType())) {
386
- node.remove();
387
- return;
388
530
  }
389
- wrapBlockModify(oldBlock, editor, () => {
390
- node.remove();
531
+ return false;
532
+ }
533
+ if ($isListItemNode(node)) {
534
+ const diffNode = $createDiffNode("listItemRemove");
535
+ node.getChildren().forEach((child) => {
536
+ diffNode.append($cloneNode(child, editor));
391
537
  });
538
+ node.clear();
539
+ node.append(diffNode);
540
+ applied = true;
541
+ } else {
542
+ const diffNode = $createDiffNode("remove");
543
+ diffNode.append($cloneNode(node, editor));
544
+ node.replace(diffNode, false);
545
+ applied = true;
392
546
  }
393
- });
547
+ } else {
548
+ const oldBlock = $closest(node, (node) => node.isInline() === false);
549
+ if (!oldBlock) throw new Error("Old block node not found for removal.");
550
+ if ($closest(node, (node) => node.getType() === DiffNode.getType())) {
551
+ node.remove();
552
+ applied = true;
553
+ return true;
554
+ }
555
+ wrapBlockModify(oldBlock, editor, () => {
556
+ node.remove();
557
+ });
558
+ applied = true;
559
+ }
560
+ return applied;
394
561
  }
395
- function handleInsert(editor, payload, dataSource) {
562
+ function handleInsert(editor, payload, dataSource, referenceNodeOverride) {
396
563
  const { litexml, delay } = payload;
397
564
  const isBefore = "beforeId" in payload;
398
565
  const inode = dataSource.readLiteXMLToInode(litexml);
399
- editor.update(() => {
400
- try {
401
- let referenceNode = null;
566
+ let insertedNodeId = null;
567
+ try {
568
+ let referenceNode = referenceNodeOverride || null;
569
+ if (!referenceNode) {
402
570
  if (isBefore) {
403
571
  if (payload.beforeId === "root") referenceNode = $getRoot().getFirstChild();
404
- else referenceNode = $getNodeByKey(charToId(payload.beforeId));
572
+ else referenceNode = $findNodeById(payload.beforeId);
405
573
  } else if (payload.afterId === "root") referenceNode = $getRoot().getLastChild();
406
- else referenceNode = $getNodeByKey(charToId(payload.afterId));
407
- if (!referenceNode) throw new Error("Reference node not found for insertion.");
408
- const newNodes = inode.root.children.map((child) => $parseSerializedNodeImpl(child, editor));
409
- const referencesTableCell = $isTableCellNode(referenceNode);
410
- const insertsOnlyTableCells = newNodes.length > 0 && newNodes.every($isTableCellNode);
411
- if (referencesTableCell || insertsOnlyTableCells) {
412
- if (!referencesTableCell || !insertsOnlyTableCells) {
413
- logger.error("❌ Table cells can only be inserted next to another cell in the same row.");
414
- return;
415
- }
416
- const cellReference = referenceNode;
417
- const table = $getTableForCell(cellReference);
418
- const referenceIndex = $getTableCellColumnIndex(cellReference);
419
- if (!table || referenceIndex < 0) {
420
- logger.error("❌ Table cell insertion requires a valid table parent.");
421
- return;
422
- }
423
- const rowWidthBefore = $getLogicalRowWidth(cellReference);
424
- const insertionIndex = isBefore ? referenceIndex : referenceIndex + cellReference.getColSpan();
425
- const insertedSpan = newNodes.reduce((total, node) => total + ($isTableCellNode(node) ? node.getColSpan() : 0), 0);
426
- $updateTableWidthsForCellInsertion(table, rowWidthBefore, insertionIndex, insertedSpan);
427
- let spanOffset = 0;
428
- const cells = newNodes.map((node) => {
429
- const result = delay ? $createTableCellDiffFromCell(editor, node, "add", `${table.getKey()}:column:${insertionIndex + spanOffset}`) : node;
430
- spanOffset += node.getColSpan();
431
- return result;
432
- });
433
- if (isBefore) cells.reverse().forEach((cell) => {
434
- referenceNode = referenceNode.insertBefore(cell);
435
- });
436
- else cells.forEach((cell) => {
437
- referenceNode = referenceNode.insertAfter(cell);
438
- });
439
- return;
574
+ else referenceNode = $findNodeById(payload.afterId);
575
+ }
576
+ if (!referenceNode) throw new Error("Reference node not found for insertion.");
577
+ const newNodes = inode.root.children.map((child) => $parseSerializedNodeImpl(child, editor));
578
+ $ensureUniqueNodeIds(newNodes);
579
+ insertedNodeId = newNodes.length ? $getNodeId(newNodes.at(-1)) || null : null;
580
+ const referencesTableCell = $isTableCellNode(referenceNode);
581
+ const insertsOnlyTableCells = newNodes.length > 0 && newNodes.every($isTableCellNode);
582
+ if (referencesTableCell || insertsOnlyTableCells) {
583
+ if (!referencesTableCell || !insertsOnlyTableCells) {
584
+ logger.error("❌ Table cells can only be inserted next to another cell in the same row.");
585
+ return null;
440
586
  }
441
- if (!delay) {
442
- if (isBefore) newNodes.reverse().forEach((node) => {
443
- referenceNode = referenceNode.insertBefore(node);
444
- });
445
- else newNodes.forEach((node) => {
446
- if (referenceNode) referenceNode = referenceNode.insertAfter(node);
447
- });
448
- return;
587
+ const cellReference = referenceNode;
588
+ const table = $getTableForCell(cellReference);
589
+ const referenceIndex = $getTableCellColumnIndex(cellReference);
590
+ if (!table || referenceIndex < 0) {
591
+ logger.error("❌ Table cell insertion requires a valid table parent.");
592
+ return null;
449
593
  }
450
- const referencesTableRow = $isTableRowNode(referenceNode);
451
- const insertsOnlyTableRows = newNodes.every($isTableRowNode);
452
- if (referencesTableRow || insertsOnlyTableRows) {
453
- if (!referencesTableRow || !insertsOnlyTableRows || !$isTableNode(referenceNode.getParent())) {
454
- logger.error("❌ Table rows can only be inserted next to another row in the same table.");
455
- return;
456
- }
457
- if (isBefore) newNodes.reverse().forEach((node) => {
458
- if (!$isTableRowNode(node)) return;
459
- const diffRow = $createTableRowDiffFromRow(editor, node, "add");
460
- referenceNode = referenceNode.insertBefore(diffRow);
461
- });
462
- else newNodes.forEach((node) => {
463
- if (!$isTableRowNode(node)) return;
464
- const diffRow = $createTableRowDiffFromRow(editor, node, "add");
465
- referenceNode = referenceNode.insertAfter(diffRow);
466
- });
467
- return;
594
+ const rowWidthBefore = $getLogicalRowWidth(cellReference);
595
+ const insertionIndex = isBefore ? referenceIndex : referenceIndex + cellReference.getColSpan();
596
+ const insertedSpan = newNodes.reduce((total, node) => total + ($isTableCellNode(node) ? node.getColSpan() : 0), 0);
597
+ $updateTableWidthsForCellInsertion(table, rowWidthBefore, insertionIndex, insertedSpan);
598
+ let spanOffset = 0;
599
+ const cells = newNodes.map((node) => {
600
+ const result = delay ? $createTableCellDiffFromCell(editor, node, "add", `${table.getKey()}:column:${insertionIndex + spanOffset}`) : node;
601
+ spanOffset += node.getColSpan();
602
+ return result;
603
+ });
604
+ if (isBefore) cells.reverse().forEach((cell) => {
605
+ referenceNode = referenceNode.insertBefore(cell);
606
+ });
607
+ else cells.forEach((cell) => {
608
+ referenceNode = referenceNode.insertAfter(cell);
609
+ });
610
+ return insertedNodeId;
611
+ }
612
+ if (!delay) {
613
+ if (isBefore) newNodes.reverse().forEach((node) => {
614
+ referenceNode = referenceNode.insertBefore(node);
615
+ });
616
+ else newNodes.forEach((node) => {
617
+ if (referenceNode) referenceNode = referenceNode.insertAfter(node);
618
+ });
619
+ return insertedNodeId;
620
+ }
621
+ const referencesTableRow = $isTableRowNode(referenceNode);
622
+ const insertsOnlyTableRows = newNodes.every($isTableRowNode);
623
+ if (referencesTableRow || insertsOnlyTableRows) {
624
+ if (!referencesTableRow || !insertsOnlyTableRows || !$isTableNode(referenceNode.getParent())) {
625
+ logger.error("❌ Table rows can only be inserted next to another row in the same table.");
626
+ return null;
468
627
  }
469
- if (isBefore) {
470
- if (referenceNode.isInline() === false) {
471
- const originDiffNode = $closest(referenceNode, (node) => node.getType() === DiffNode.getType());
472
- if (originDiffNode) referenceNode = originDiffNode;
473
- newNodes.map((node) => {
474
- if ($isListItemNode(node)) {
475
- const diffNode = $createDiffNode("listItemAdd");
476
- node.getChildren().forEach((child) => diffNode.append(child));
477
- return node.append(diffNode);
478
- }
479
- const diffNode = $createDiffNode("add");
480
- diffNode.append(node);
481
- return diffNode;
482
- }).reverse().forEach((diffNode) => {
483
- if (referenceNode) referenceNode = referenceNode.insertBefore(diffNode);
484
- });
485
- } else {
486
- const refBlock = $closest(referenceNode, (node) => node.isInline() === false);
487
- if (!refBlock) throw new Error("Reference block node not found for insertion.");
488
- if ($closest(referenceNode, (node) => node.getType() === DiffNode.getType())) newNodes.forEach((node) => {
489
- if (referenceNode) referenceNode = referenceNode.insertBefore(node);
490
- });
491
- else wrapBlockModify(refBlock, editor, () => {
492
- newNodes.forEach((node) => {
493
- if (referenceNode) referenceNode = referenceNode.insertBefore(node);
494
- });
495
- });
496
- }
497
- } else if (referenceNode.isInline() === false) {
628
+ if (isBefore) newNodes.reverse().forEach((node) => {
629
+ if (!$isTableRowNode(node)) return;
630
+ const diffRow = $createTableRowDiffFromRow(editor, node, "add");
631
+ referenceNode = referenceNode.insertBefore(diffRow);
632
+ });
633
+ else newNodes.forEach((node) => {
634
+ if (!$isTableRowNode(node)) return;
635
+ const diffRow = $createTableRowDiffFromRow(editor, node, "add");
636
+ referenceNode = referenceNode.insertAfter(diffRow);
637
+ });
638
+ return insertedNodeId;
639
+ }
640
+ if (isBefore) {
641
+ if (referenceNode.isInline() === false) {
498
642
  const originDiffNode = $closest(referenceNode, (node) => node.getType() === DiffNode.getType());
499
643
  if (originDiffNode) referenceNode = originDiffNode;
500
- newNodes.forEach((node) => {
501
- if (referenceNode) {
502
- if ($isListItemNode(node)) {
503
- const diffNode = $createDiffNode("listItemAdd");
504
- node.getChildren().forEach((child) => {
505
- diffNode.append(child);
506
- });
507
- node.append(diffNode);
508
- referenceNode = referenceNode.insertAfter(node);
509
- } else {
510
- const diffNode = $createDiffNode("add");
511
- diffNode.append(node);
512
- referenceNode = referenceNode.insertAfter(diffNode);
513
- }
644
+ newNodes.map((node) => {
645
+ if ($isListItemNode(node)) {
646
+ const diffNode = $createDiffNode("listItemAdd");
647
+ node.getChildren().forEach((child) => diffNode.append(child));
648
+ return node.append(diffNode);
514
649
  }
650
+ const diffNode = $createDiffNode("add");
651
+ diffNode.append(node);
652
+ return diffNode;
653
+ }).reverse().forEach((diffNode) => {
654
+ if (referenceNode) referenceNode = referenceNode.insertBefore(diffNode);
515
655
  });
516
656
  } else {
517
657
  const refBlock = $closest(referenceNode, (node) => node.isInline() === false);
518
658
  if (!refBlock) throw new Error("Reference block node not found for insertion.");
519
659
  if ($closest(referenceNode, (node) => node.getType() === DiffNode.getType())) newNodes.forEach((node) => {
520
- if (referenceNode) referenceNode = referenceNode.insertAfter(node);
660
+ if (referenceNode) referenceNode = referenceNode.insertBefore(node);
521
661
  });
522
662
  else wrapBlockModify(refBlock, editor, () => {
523
663
  newNodes.forEach((node) => {
524
- if (referenceNode) referenceNode = referenceNode.insertAfter(node);
664
+ if (referenceNode) referenceNode = referenceNode.insertBefore(node);
525
665
  });
526
666
  });
527
667
  }
528
- } catch (error) {
529
- logger.error("❌ Error inserting node:", error);
668
+ } else if (referenceNode.isInline() === false) {
669
+ const originDiffNode = $closest(referenceNode, (node) => node.getType() === DiffNode.getType());
670
+ if (originDiffNode) referenceNode = originDiffNode;
671
+ newNodes.forEach((node) => {
672
+ if (referenceNode) {
673
+ if ($isListItemNode(node)) {
674
+ const diffNode = $createDiffNode("listItemAdd");
675
+ node.getChildren().forEach((child) => {
676
+ diffNode.append(child);
677
+ });
678
+ node.append(diffNode);
679
+ referenceNode = referenceNode.insertAfter(node);
680
+ } else {
681
+ const diffNode = $createDiffNode("add");
682
+ diffNode.append(node);
683
+ referenceNode = referenceNode.insertAfter(diffNode);
684
+ }
685
+ }
686
+ });
687
+ } else {
688
+ const refBlock = $closest(referenceNode, (node) => node.isInline() === false);
689
+ if (!refBlock) throw new Error("Reference block node not found for insertion.");
690
+ if ($closest(referenceNode, (node) => node.getType() === DiffNode.getType())) newNodes.forEach((node) => {
691
+ if (referenceNode) referenceNode = referenceNode.insertAfter(node);
692
+ });
693
+ else wrapBlockModify(refBlock, editor, () => {
694
+ newNodes.forEach((node) => {
695
+ if (referenceNode) referenceNode = referenceNode.insertAfter(node);
696
+ });
697
+ });
530
698
  }
531
- });
699
+ } catch (error) {
700
+ logger.error("❌ Error inserting node:", error);
701
+ insertedNodeId = null;
702
+ }
703
+ return insertedNodeId;
532
704
  }
533
705
  //#endregion
534
- export { LITEXML_APPLY_COMMAND, LITEXML_INSERT_COMMAND, LITEXML_MODIFY_COMMAND, LITEXML_REMOVE_COMMAND, registerLiteXMLCommand };
706
+ export { LITEXML_APPLY_COMMAND, LITEXML_INSERT_COMMAND, LITEXML_MODIFY_COMMAND, LITEXML_MODIFY_WITH_RESULTS_COMMAND, LITEXML_REMOVE_COMMAND, registerLiteXMLCommand };