@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
@@ -3,18 +3,271 @@ import EventEmitter from "eventemitter3";
3
3
  //#region src/plugins/link/service/i-link-service.ts
4
4
  init_utils();
5
5
  const ILinkService = genServiceId("LinkService");
6
+ const DEFAULT_LABELS = {
7
+ convertToBlockCard: "Convert to block card",
8
+ convertToCard: "Convert to card",
9
+ convertToIframe: "Convert to iframe",
10
+ convertToLink: "Convert to link",
11
+ convertToSchema: "Convert to schema"
12
+ };
13
+ var LinkProtocolPolicy = class {
14
+ constructor() {
15
+ this.allowedProtocols = /* @__PURE__ */ new Set([
16
+ "http:",
17
+ "https:",
18
+ "mailto:",
19
+ "sms:",
20
+ "tel:"
21
+ ]);
22
+ }
23
+ getAllowedProtocols() {
24
+ return this.allowedProtocols;
25
+ }
26
+ setAllowedProtocols(protocols) {
27
+ this.allowedProtocols = new Set(protocols.map(normalizeProtocol));
28
+ }
29
+ };
30
+ var LinkRuleRegistry = class {
31
+ constructor() {
32
+ this.embedRules = [];
33
+ this.schemaRules = [];
34
+ }
35
+ getEmbedRule(url, context) {
36
+ return this.embedRules.find((rule) => rule.match(url, context)) || null;
37
+ }
38
+ getSchemaRule(url, context, parseSchemaUrl) {
39
+ const schema = context.schema === void 0 ? parseSchemaUrl(url) : context.schema;
40
+ return this.schemaRules.find((rule) => rule.match(url, {
41
+ ...context,
42
+ schema
43
+ })) || null;
44
+ }
45
+ getSchemaRuleById(schemaType) {
46
+ return this.schemaRules.find((rule) => rule.id === schemaType) || null;
47
+ }
48
+ setEmbedRules(rules = []) {
49
+ this.embedRules = rules;
50
+ }
51
+ setSchemaRules(rules = []) {
52
+ this.schemaRules = rules;
53
+ }
54
+ };
55
+ var LegacySchemaLinkRegistry = class {
56
+ constructor() {
57
+ this.protocols = /* @__PURE__ */ new Set();
58
+ }
59
+ hasSchemaLinkRenderer(url) {
60
+ try {
61
+ const { protocol } = new URL(url);
62
+ return this.protocols.has(protocol);
63
+ } catch {
64
+ return false;
65
+ }
66
+ }
67
+ setSchemaLinkRenderers(renderers = []) {
68
+ this.protocols = new Set(renderers.map(({ protocol }) => normalizeProtocol(protocol)));
69
+ }
70
+ };
71
+ var LinkToolbarController = class {
72
+ constructor() {
73
+ this.baseEnabled = true;
74
+ this.labels = DEFAULT_LABELS;
75
+ this.toolbarActions = [];
76
+ this.toolbarSuppressions = /* @__PURE__ */ new Set();
77
+ }
78
+ get enabled() {
79
+ return this.baseEnabled && this.toolbarSuppressions.size === 0;
80
+ }
81
+ getLabels() {
82
+ return this.labels;
83
+ }
84
+ getToolbarActions(context) {
85
+ return this.toolbarActions.filter((action) => action.visible?.(context) ?? true);
86
+ }
87
+ setLabels(labels) {
88
+ this.labels = {
89
+ ...DEFAULT_LABELS,
90
+ ...labels
91
+ };
92
+ }
93
+ setToolbarActions(actions = []) {
94
+ this.toolbarActions = actions;
95
+ }
96
+ setEnabled(enable) {
97
+ const previousEnabled = this.enabled;
98
+ this.baseEnabled = enable;
99
+ return previousEnabled !== this.enabled;
100
+ }
101
+ suppress(reason = "unknown") {
102
+ const previousEnabled = this.enabled;
103
+ const token = Symbol(reason);
104
+ this.toolbarSuppressions.add(token);
105
+ return {
106
+ changed: previousEnabled !== this.enabled,
107
+ token
108
+ };
109
+ }
110
+ restore(token) {
111
+ const previousEnabled = this.enabled;
112
+ if (!this.toolbarSuppressions.delete(token)) return false;
113
+ return previousEnabled !== this.enabled;
114
+ }
115
+ };
6
116
  var LinkService = class extends EventEmitter {
7
117
  constructor(..._args) {
8
118
  super(..._args);
9
- this._enableLinkToolbar = true;
119
+ this.cardMetadataLoadingKeys = /* @__PURE__ */ new Set();
120
+ this.protocolPolicy = new LinkProtocolPolicy();
121
+ this.legacySchemaLinkRegistry = new LegacySchemaLinkRegistry();
122
+ this.ruleRegistry = new LinkRuleRegistry();
123
+ this.toolbarController = new LinkToolbarController();
124
+ this.toolbarItems = /* @__PURE__ */ new Map();
10
125
  }
11
126
  get enableLinkToolbar() {
12
- return this._enableLinkToolbar;
127
+ return this.toolbarController.enabled;
128
+ }
129
+ getAllowedProtocols() {
130
+ return this.protocolPolicy.getAllowedProtocols();
131
+ }
132
+ getEmbedRule(url, context) {
133
+ return this.ruleRegistry.getEmbedRule(url, context);
134
+ }
135
+ getLabels() {
136
+ return this.toolbarController.getLabels();
137
+ }
138
+ hasSchemaLinkRenderer(url) {
139
+ return this.legacySchemaLinkRegistry.hasSchemaLinkRenderer(url);
140
+ }
141
+ isCardMetadataLoading(nodeKey) {
142
+ return this.cardMetadataLoadingKeys.has(nodeKey);
143
+ }
144
+ setCardMetadataLoading(nodeKey, loading) {
145
+ if (!(loading ? !this.cardMetadataLoadingKeys.has(nodeKey) : this.cardMetadataLoadingKeys.has(nodeKey))) return;
146
+ if (loading) this.cardMetadataLoadingKeys.add(nodeKey);
147
+ else this.cardMetadataLoadingKeys.delete(nodeKey);
148
+ this.emit("cardMetadataChange");
149
+ }
150
+ getSchemaRule(url, context) {
151
+ return this.ruleRegistry.getSchemaRule(url, context, (url) => this.parseSchemaUrl(url));
152
+ }
153
+ getToolbarActions(context) {
154
+ return this.toolbarController.getToolbarActions(context);
155
+ }
156
+ parseSchemaUrl(url) {
157
+ try {
158
+ const parsedUrl = new URL(url);
159
+ return {
160
+ hash: parsedUrl.hash,
161
+ host: parsedUrl.host,
162
+ params: Object.fromEntries(parsedUrl.searchParams.entries()),
163
+ pathname: parsedUrl.pathname,
164
+ protocol: parsedUrl.protocol,
165
+ raw: url,
166
+ search: parsedUrl.search
167
+ };
168
+ } catch {
169
+ return null;
170
+ }
171
+ }
172
+ updateConfig(config) {
173
+ this.setAllowedProtocols([
174
+ "http:",
175
+ "https:",
176
+ "mailto:",
177
+ "sms:",
178
+ "tel:",
179
+ ...config?.schemaLinkRenderers?.map(({ protocol }) => protocol) || [],
180
+ ...config?.allowedProtocols || []
181
+ ]);
182
+ this.setEmbedRules(config?.linkEmbedRules);
183
+ this.setLabels(config?.labels);
184
+ this.setSchemaLinkRenderers(config?.schemaLinkRenderers);
185
+ this.setSchemaRules([...config?.schemaRules || [], ...createLegacySchemaRules(config?.schemaLinkRenderers)]);
186
+ this.setToolbarActions(config?.toolbarActions);
187
+ }
188
+ setAllowedProtocols(protocols) {
189
+ this.protocolPolicy.setAllowedProtocols(protocols);
190
+ }
191
+ setEmbedRules(rules = []) {
192
+ this.ruleRegistry.setEmbedRules(rules);
193
+ }
194
+ setLabels(labels) {
195
+ this.toolbarController.setLabels(labels);
196
+ }
197
+ setSchemaLinkRenderers(renderers = []) {
198
+ this.legacySchemaLinkRegistry.setSchemaLinkRenderers(renderers);
199
+ }
200
+ setSchemaRules(rules = []) {
201
+ this.ruleRegistry.setSchemaRules(rules);
202
+ }
203
+ setToolbarActions(actions = []) {
204
+ this.toolbarController.setToolbarActions(actions);
205
+ this.emit("change");
206
+ }
207
+ getToolbarItems(context) {
208
+ return Array.from(this.toolbarItems.values()).filter((item) => item.when ? item.when(context) : true).sort((a, b) => (a.order || 0) - (b.order || 0));
209
+ }
210
+ registerToolbarItem(item) {
211
+ this.toolbarItems.set(item.key, item);
212
+ this.emit("change");
213
+ return () => {
214
+ this.toolbarItems.delete(item.key);
215
+ this.emit("change");
216
+ };
13
217
  }
14
218
  setLinkToolbar(enable) {
15
- this._enableLinkToolbar = enable;
16
- this.emit("linkToolbarChange", enable);
219
+ if (this.toolbarController.setEnabled(enable)) this.emit("linkToolbarChange", enable);
220
+ }
221
+ suppressLinkToolbar(reason = "unknown") {
222
+ const { changed, token } = this.toolbarController.suppress(reason);
223
+ if (changed) this.emit("linkToolbarChange", this.toolbarController.enabled);
224
+ return token;
225
+ }
226
+ restoreLinkToolbar(token) {
227
+ if (this.toolbarController.restore(token)) this.emit("linkToolbarChange", this.toolbarController.enabled);
228
+ }
229
+ subscribe(listener) {
230
+ this.on("change", listener);
231
+ return () => {
232
+ this.off("change", listener);
233
+ };
234
+ }
235
+ subscribeCardMetadata(listener) {
236
+ this.on("cardMetadataChange", listener);
237
+ return () => {
238
+ this.off("cardMetadataChange", listener);
239
+ };
17
240
  }
18
241
  };
242
+ function normalizeProtocol(protocol) {
243
+ return `${protocol.split(":")[0]}:`;
244
+ }
245
+ function createLegacySchemaRules(renderers = []) {
246
+ return renderers.map(({ protocol }) => {
247
+ const normalizedProtocol = normalizeProtocol(protocol);
248
+ return {
249
+ id: normalizedProtocol.replace(":", ""),
250
+ match: (url) => {
251
+ try {
252
+ return new URL(url).protocol === normalizedProtocol;
253
+ } catch {
254
+ return false;
255
+ }
256
+ }
257
+ };
258
+ });
259
+ }
260
+ function getNodeUrl(node) {
261
+ const maybeUrlNode = node;
262
+ if (typeof maybeUrlNode.getURL === "function") return maybeUrlNode.getURL();
263
+ return "";
264
+ }
265
+ function getNodeTitle(node) {
266
+ const maybeTitleNode = node;
267
+ if (typeof maybeTitleNode.getTitle === "function") return maybeTitleNode.getTitle() || getNodeUrl(node);
268
+ const maybeTextNode = node;
269
+ if (typeof maybeTextNode.getTextContent === "function") return maybeTextNode.getTextContent() || getNodeUrl(node);
270
+ return getNodeUrl(node);
271
+ }
19
272
  //#endregion
20
- export { ILinkService, LinkService };
273
+ export { ILinkService, LinkService, createLegacySchemaRules, getNodeTitle, getNodeUrl, normalizeProtocol };
@@ -119,7 +119,11 @@ const ListPlugin = class extends KernelPlugin {
119
119
  type: "element"
120
120
  });
121
121
  markdownService.registerMarkdownWriter(ListNode.getType(), (ctx, node) => {
122
- if ($isListNode(node)) ctx.wrap("", "\n");
122
+ if ($isListNode(node)) {
123
+ const parent = node.getParent();
124
+ const needsLeadingNewline = $isListItemNode(parent) && node.getPreviousSibling() !== null;
125
+ ctx.wrap(needsLeadingNewline ? "\n" : "", "\n");
126
+ }
123
127
  });
124
128
  const getLevel = (node) => {
125
129
  if (!node) return 0;
@@ -150,7 +154,10 @@ const ListPlugin = class extends KernelPlugin {
150
154
  let start = node.start || 1;
151
155
  return INodeHelper.createElementNode("list", {
152
156
  children: children.map((v) => {
153
- if (v.type === "listitem") v.value = start++;
157
+ if (v.type === "listitem") {
158
+ v.value = start;
159
+ if (v.children?.[0]?.type !== "list") start++;
160
+ }
154
161
  return v;
155
162
  }),
156
163
  direction: "ltr",
@@ -1,13 +1,76 @@
1
+ import { $isDiffContentNode } from "../node/DiffContentNode.js";
1
2
  import { DiffNode } from "../node/DiffNode.js";
3
+ import { $isTableCellDiffNode } from "../node/TableCellDiffNode.js";
4
+ import { $createPlainTableCellFromDiff, $getTableCellColumnIndex, $getTableCellDiffGroup, $getTableForCell, $removeTableWidthsForCompleteCellGroup, $shrinkTableWidthsAfterCellRemoval } from "../table-cell-diff.js";
5
+ import { $isTableRowDiffNode } from "../node/TableRowDiffNode.js";
6
+ import { $createPlainTableRowFromDiff, $getTableRowDiffPair } from "../table-row-diff.js";
2
7
  import { DiffAction, LITEXML_DIFFNODE_ALL_COMMAND, LITEXML_DIFFNODE_COMMAND } from "./symbols.js";
3
8
  import { $getNodeByKey, $isElementNode, COMMAND_PRIORITY_EDITOR } from "lexical";
4
9
  import { mergeRegister } from "@lexical/utils";
5
10
  //#region src/plugins/litexml/command/diffCommand.ts
6
- function doAction(node, action) {
11
+ function doTableRowAction(editor, node, action) {
12
+ const pair = $getTableRowDiffPair(node);
13
+ if (pair) {
14
+ const survivingRow = action === 1 ? pair.add : pair.remove;
15
+ const discardedRow = action === 1 ? pair.remove : pair.add;
16
+ const plainRow = $createPlainTableRowFromDiff(editor, survivingRow);
17
+ discardedRow.remove();
18
+ survivingRow.replace(plainRow, false);
19
+ plainRow.selectStart();
20
+ return;
21
+ }
22
+ if (node.getDiffType() === "remove") {
23
+ if (action === 1) node.remove();
24
+ else {
25
+ const plainRow = $createPlainTableRowFromDiff(editor, node);
26
+ node.replace(plainRow, false);
27
+ plainRow.selectStart();
28
+ }
29
+ return;
30
+ }
31
+ if (action === 1) {
32
+ const plainRow = $createPlainTableRowFromDiff(editor, node);
33
+ node.replace(plainRow, false);
34
+ plainRow.selectStart();
35
+ } else node.remove();
36
+ }
37
+ function doTableCellAction(editor, node, action) {
38
+ const group = $getTableCellDiffGroup(node);
39
+ const table = $getTableForCell(node);
40
+ const columnIndex = $getTableCellColumnIndex(node);
41
+ const span = node.getColSpan();
42
+ if (node.getDiffType() === "add" && action === 1 || node.getDiffType() === "remove" && action === 0) {
43
+ group.forEach((cell) => cell.replace($createPlainTableCellFromDiff(editor, cell), false));
44
+ return;
45
+ }
46
+ if (table && columnIndex >= 0) {
47
+ const widthCount = table.getColWidths()?.length;
48
+ $removeTableWidthsForCompleteCellGroup(table, group, columnIndex, span);
49
+ group.forEach((cell) => cell.remove());
50
+ if (table.getColWidths()?.length === widthCount) $shrinkTableWidthsAfterCellRemoval(table, columnIndex, span);
51
+ return;
52
+ }
53
+ group.forEach((cell) => cell.remove());
54
+ }
55
+ function doAction(editor, node, action) {
56
+ if ($isTableRowDiffNode(node)) {
57
+ doTableRowAction(editor, node, action);
58
+ return;
59
+ }
60
+ const parent = node.getParent();
61
+ if ($isTableCellDiffNode(parent)) {
62
+ doTableCellAction(editor, parent, action);
63
+ return;
64
+ }
7
65
  if (node.diffType === "modify") {
8
66
  const children = node.getChildren();
9
- if (action === 1) node.replace(children[1], false).selectEnd();
10
- else if (action === 0) node.replace(children[0], false).selectEnd();
67
+ const selectedChild = action === 1 ? children[1] : children[0];
68
+ if ($isDiffContentNode(selectedChild)) {
69
+ const parent = node.getParentOrThrow();
70
+ selectedChild.getChildren().forEach((child) => node.insertBefore(child));
71
+ node.remove();
72
+ if ($isElementNode(parent)) parent.selectEnd();
73
+ } else if (selectedChild) node.replace(selectedChild, false).selectEnd();
11
74
  }
12
75
  if (node.diffType === "remove") {
13
76
  if (action === 1) node.remove();
@@ -19,7 +82,10 @@ function doAction(node, action) {
19
82
  if (node.diffType === "add") {
20
83
  if (action === 1) {
21
84
  const children = node.getChildren();
22
- node.replace(children[0], false).selectEnd();
85
+ const parent = node.getParentOrThrow();
86
+ children.forEach((child) => node.insertBefore(child));
87
+ node.remove();
88
+ if ($isElementNode(parent)) parent.selectEnd();
23
89
  } else if (action === 0) node.remove();
24
90
  }
25
91
  if (node.diffType === "listItemModify") {
@@ -65,23 +131,37 @@ function doAction(node, action) {
65
131
  function registerLiteXMLDiffCommand(editor) {
66
132
  return mergeRegister(editor.registerCommand(LITEXML_DIFFNODE_COMMAND, (payload) => {
67
133
  const { action, nodeKey } = payload;
68
- const node = editor.getEditorState().read(() => {
69
- return $getNodeByKey(nodeKey);
70
- });
71
- if (!node) return false;
72
134
  editor.update(() => {
73
- doAction(node, action);
135
+ const node = $getNodeByKey(nodeKey);
136
+ if (!(node instanceof DiffNode) && !$isTableRowDiffNode(node)) return;
137
+ doAction(editor, node, action);
74
138
  });
75
139
  return false;
76
140
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(LITEXML_DIFFNODE_ALL_COMMAND, (payload) => {
77
141
  const { action } = payload;
78
- const nodes = editor.getEditorState().read(() => {
79
- return Array.from(editor._editorState._nodeMap.values()).filter((n) => n instanceof DiffNode && !!n.getParent());
142
+ const nodeKeys = editor.getEditorState().read(() => {
143
+ return Array.from(editor._editorState._nodeMap.values()).filter((node) => (node instanceof DiffNode || $isTableRowDiffNode(node)) && !!node.getParent()).map((node) => node.getKey());
80
144
  });
81
- if (!nodes.length) return false;
145
+ if (!nodeKeys.length) return false;
82
146
  editor.update(() => {
83
- nodes.forEach((node) => {
84
- doAction(node, action);
147
+ const handled = /* @__PURE__ */ new Set();
148
+ nodeKeys.forEach((nodeKey) => {
149
+ if (handled.has(nodeKey)) return;
150
+ const node = $getNodeByKey(nodeKey);
151
+ if (!(node instanceof DiffNode) && !$isTableRowDiffNode(node)) return;
152
+ const pair = $isTableRowDiffNode(node) ? $getTableRowDiffPair(node) : null;
153
+ if (pair) {
154
+ handled.add(pair.remove.getKey());
155
+ handled.add(pair.add.getKey());
156
+ } else {
157
+ const parent = node instanceof DiffNode ? node.getParent() : null;
158
+ if ($isTableCellDiffNode(parent)) $getTableCellDiffGroup(parent).forEach((cell) => {
159
+ const diff = cell.getFirstChild();
160
+ if (diff instanceof DiffNode) handled.add(diff.getKey());
161
+ });
162
+ handled.add(nodeKey);
163
+ }
164
+ doAction(editor, node, action);
85
165
  });
86
166
  });
87
167
  return false;
@@ -1,8 +1,14 @@
1
1
  import { createDebugLogger, init_debug } from "../../../utils/debug.js";
2
- import { $closest, init_utils } from "../../../editor-kernel/utils.js";
3
- import { $createDiffNode, DiffNode } from "../node/DiffNode.js";
2
+ import { $closest, getKernelFromEditor, init_utils } from "../../../editor-kernel/utils.js";
3
+ import { findNewIllegalDiffPaths, projectLiteXmlOperation } from "../diff-validation.js";
4
+ import { $createDiffContentNode, $isDiffContentNode } from "../node/DiffContentNode.js";
5
+ import { $createDiffNode, $isDiffNode, DiffNode } from "../node/DiffNode.js";
6
+ import { $isTableCellDiffNode } from "../node/TableCellDiffNode.js";
4
7
  import { $cloneNode, $parseSerializedNodeImpl, charToId } from "../utils/index.js";
8
+ import { $createTableCellDiffFromCell, $getLogicalRowWidth, $getTableCellColumnIndex, $getTableForCell, $shrinkTableWidthsAfterCellRemoval, $updateTableWidthsForCellInsertion } from "../table-cell-diff.js";
9
+ import { $areTableRowStructuresCompatible, $createTableRowDiffFromRow } from "../table-row-diff.js";
5
10
  import { LITEXML_APPLY_COMMAND, LITEXML_INSERT_COMMAND, LITEXML_MODIFY_COMMAND, LITEXML_REMOVE_COMMAND } from "./symbols.js";
11
+ import { $isTableCellNode, $isTableNode, $isTableRowNode } from "@lexical/table";
6
12
  import { $createParagraphNode, $getNodeByKey, $getRoot, $insertNodes, $isElementNode, COMMAND_PRIORITY_EDITOR } from "lexical";
7
13
  import { mergeRegister } from "@lexical/utils";
8
14
  import { $isListItemNode } from "@lexical/list";
@@ -13,6 +19,34 @@ const logger = createDebugLogger("plugin", "litexml");
13
19
  function toArrayXml(litexml) {
14
20
  return Array.isArray(litexml) ? litexml : [litexml];
15
21
  }
22
+ function hasNewIllegalDiffs(previous, projected) {
23
+ return findNewIllegalDiffPaths(previous, projected);
24
+ }
25
+ function projectOperation(dataSource, document, operation) {
26
+ try {
27
+ const projected = projectLiteXmlOperation(document, operation, (xml) => dataSource.readLiteXMLToInode(xml));
28
+ const newIllegalDiffs = hasNewIllegalDiffs(document, projected);
29
+ if (newIllegalDiffs.length > 0) {
30
+ logger.warn("⚠️ Skipping operation with illegal nested diff", newIllegalDiffs);
31
+ return null;
32
+ }
33
+ return projected;
34
+ } catch (error) {
35
+ logger.error("❌ Failed to preflight LiteXML operation:", error);
36
+ return null;
37
+ }
38
+ }
39
+ 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
+ return operation;
49
+ }
16
50
  function tryParseChild(child, editor) {
17
51
  try {
18
52
  const oldNode = $getNodeByKey(child.id);
@@ -29,6 +63,47 @@ function tryParseChild(child, editor) {
29
63
  }
30
64
  }
31
65
  function handleReplaceForApplyDelay(oldNode, newNode, modifyBlockNodes, diffNodeMap, editor) {
66
+ if ($isTableRowNode(oldNode) || $isTableRowNode(newNode)) {
67
+ if (!$isTableRowNode(oldNode) || !$isTableRowNode(newNode) || !$isTableNode(oldNode.getParent()) || !$areTableRowStructuresCompatible(oldNode, newNode)) {
68
+ logger.error(`❌ Invalid table row modification for row ${oldNode.getKey()}.`);
69
+ return;
70
+ }
71
+ const changeId = `${oldNode.getKey()}:${newNode.getKey()}`;
72
+ const removeRow = $createTableRowDiffFromRow(editor, oldNode, "remove", changeId);
73
+ const addRow = $createTableRowDiffFromRow(editor, newNode, "add", changeId);
74
+ oldNode.replace(removeRow, false);
75
+ removeRow.insertAfter(addRow);
76
+ return;
77
+ }
78
+ if ($isTableCellNode(oldNode) && $isTableCellNode(newNode)) {
79
+ const existingDiff = oldNode.getChildren().find($isDiffNode);
80
+ if (existingDiff) {
81
+ if ($isTableCellDiffNode(oldNode)) {
82
+ existingDiff.clear();
83
+ newNode.getChildren().forEach((child) => {
84
+ existingDiff.append($cloneNode(child, editor));
85
+ });
86
+ return;
87
+ }
88
+ const after = existingDiff.getChildren().find((child) => $isDiffContentNode(child) && child.side === "after");
89
+ if (existingDiff.diffType === "modify" && after && $isDiffContentNode(after)) {
90
+ after.clear();
91
+ newNode.getChildren().forEach((child) => {
92
+ after.append($cloneNode(child, editor));
93
+ });
94
+ return;
95
+ }
96
+ }
97
+ const before = $createDiffContentNode("before");
98
+ const after = $createDiffContentNode("after");
99
+ oldNode.getChildren().forEach((child) => before.append($cloneNode(child, editor)));
100
+ newNode.getChildren().forEach((child) => after.append($cloneNode(child, editor)));
101
+ const diffNode = $createDiffNode("modify");
102
+ diffNode.append(before, after);
103
+ oldNode.clear();
104
+ oldNode.append(diffNode);
105
+ return;
106
+ }
32
107
  const oldBlock = $closest(oldNode, (node) => node.isInline() === false);
33
108
  if (!oldBlock) throw new Error("Old block node not found for diffing.");
34
109
  if ($closest(oldNode, (node) => node.getType() === DiffNode.getType())) {
@@ -112,8 +187,15 @@ function registerLiteXMLCommand(editor, dataSource) {
112
187
  else acc.push(cur);
113
188
  return acc;
114
189
  }, []);
190
+ let projectedDocument = getKernelFromEditor(editor).getDocument("json");
191
+ const safePayload = resultPayload.filter((item) => {
192
+ const nextProjection = projectOperation(dataSource, projectedDocument, toProjectionOperation(item));
193
+ if (!nextProjection) return false;
194
+ projectedDocument = nextProjection;
195
+ return true;
196
+ });
115
197
  try {
116
- resultPayload.forEach((item) => {
198
+ safePayload.forEach((item) => {
117
199
  const { action } = item;
118
200
  switch (action) {
119
201
  case "modify": {
@@ -142,14 +224,39 @@ function registerLiteXMLCommand(editor, dataSource) {
142
224
  }
143
225
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(LITEXML_APPLY_COMMAND, (payload) => {
144
226
  const { litexml, delay } = payload;
145
- handleModify(editor, dataSource, toArrayXml(litexml), delay);
227
+ const arrayXml = toArrayXml(litexml);
228
+ if (!delay) {
229
+ handleModify(editor, dataSource, arrayXml, delay);
230
+ return false;
231
+ }
232
+ const operation = {
233
+ action: "modify",
234
+ litexml
235
+ };
236
+ if (projectOperation(dataSource, getKernelFromEditor(editor).getDocument("json"), toProjectionOperation(operation))) handleModify(editor, dataSource, arrayXml, delay);
146
237
  return false;
147
238
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(LITEXML_REMOVE_COMMAND, (payload) => {
148
239
  const { id, delay } = payload;
149
- handleRemove(editor, charToId(id), delay);
240
+ const key = charToId(id);
241
+ if (!delay) {
242
+ handleRemove(editor, key, delay);
243
+ return false;
244
+ }
245
+ const operation = {
246
+ action: "remove",
247
+ id
248
+ };
249
+ if (projectOperation(dataSource, getKernelFromEditor(editor).getDocument("json"), toProjectionOperation(operation))) handleRemove(editor, key, delay);
150
250
  return false;
151
251
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(LITEXML_INSERT_COMMAND, (payload) => {
152
- handleInsert(editor, payload, dataSource);
252
+ if (!payload.delay) {
253
+ handleInsert(editor, payload, dataSource);
254
+ return false;
255
+ }
256
+ if (projectOperation(dataSource, getKernelFromEditor(editor).getDocument("json"), toProjectionOperation({
257
+ action: "insert",
258
+ ...payload
259
+ }))) handleInsert(editor, payload, dataSource);
153
260
  return false;
154
261
  }, COMMAND_PRIORITY_EDITOR));
155
262
  }
@@ -202,9 +309,32 @@ function handleRemove(editor, key, delay) {
202
309
  const node = $getNodeByKey(key);
203
310
  if (!node) return;
204
311
  if (!delay) {
312
+ if ($isTableCellNode(node)) {
313
+ const table = $getTableForCell(node);
314
+ const columnIndex = $getTableCellColumnIndex(node);
315
+ const span = node.getColSpan();
316
+ node.remove();
317
+ if (table && columnIndex >= 0) $shrinkTableWidthsAfterCellRemoval(table, columnIndex, span);
318
+ return;
319
+ }
205
320
  node.remove();
206
321
  return;
207
322
  }
323
+ if ($isTableCellNode(node)) {
324
+ const table = $getTableForCell(node);
325
+ const columnIndex = $getTableCellColumnIndex(node);
326
+ if (!table || columnIndex < 0) {
327
+ logger.error(`❌ Table cell ${node.getKey()} is not attached to a valid table row.`);
328
+ return;
329
+ }
330
+ const changeId = `${table.getKey()}:column:${columnIndex}`;
331
+ node.replace($createTableCellDiffFromCell(editor, node, "remove", changeId), false);
332
+ return;
333
+ }
334
+ if ($isTableRowNode(node) && $isTableNode(node.getParent())) {
335
+ node.replace($createTableRowDiffFromRow(editor, node, "remove"), false);
336
+ return;
337
+ }
208
338
  if (node.isInline() === false) {
209
339
  const originDiffNode = $closest(node, (node) => node.getType() === DiffNode.getType());
210
340
  if (originDiffNode) {
@@ -266,6 +396,38 @@ function handleInsert(editor, payload, dataSource) {
266
396
  else referenceNode = $getNodeByKey(charToId(payload.afterId));
267
397
  if (!referenceNode) throw new Error("Reference node not found for insertion.");
268
398
  const newNodes = inode.root.children.map((child) => $parseSerializedNodeImpl(child, editor));
399
+ const referencesTableCell = $isTableCellNode(referenceNode);
400
+ const insertsOnlyTableCells = newNodes.length > 0 && newNodes.every($isTableCellNode);
401
+ if (referencesTableCell || insertsOnlyTableCells) {
402
+ if (!referencesTableCell || !insertsOnlyTableCells) {
403
+ logger.error("❌ Table cells can only be inserted next to another cell in the same row.");
404
+ return;
405
+ }
406
+ const cellReference = referenceNode;
407
+ const table = $getTableForCell(cellReference);
408
+ const referenceIndex = $getTableCellColumnIndex(cellReference);
409
+ if (!table || referenceIndex < 0) {
410
+ logger.error("❌ Table cell insertion requires a valid table parent.");
411
+ return;
412
+ }
413
+ const rowWidthBefore = $getLogicalRowWidth(cellReference);
414
+ const insertionIndex = isBefore ? referenceIndex : referenceIndex + cellReference.getColSpan();
415
+ const insertedSpan = newNodes.reduce((total, node) => total + ($isTableCellNode(node) ? node.getColSpan() : 0), 0);
416
+ $updateTableWidthsForCellInsertion(table, rowWidthBefore, insertionIndex, insertedSpan);
417
+ let spanOffset = 0;
418
+ const cells = newNodes.map((node) => {
419
+ const result = delay ? $createTableCellDiffFromCell(editor, node, "add", `${table.getKey()}:column:${insertionIndex + spanOffset}`) : node;
420
+ spanOffset += node.getColSpan();
421
+ return result;
422
+ });
423
+ if (isBefore) cells.reverse().forEach((cell) => {
424
+ referenceNode = referenceNode.insertBefore(cell);
425
+ });
426
+ else cells.forEach((cell) => {
427
+ referenceNode = referenceNode.insertAfter(cell);
428
+ });
429
+ return;
430
+ }
269
431
  if (!delay) {
270
432
  if (isBefore) newNodes.reverse().forEach((node) => {
271
433
  referenceNode = referenceNode.insertBefore(node);
@@ -275,6 +437,25 @@ function handleInsert(editor, payload, dataSource) {
275
437
  });
276
438
  return;
277
439
  }
440
+ const referencesTableRow = $isTableRowNode(referenceNode);
441
+ const insertsOnlyTableRows = newNodes.every($isTableRowNode);
442
+ if (referencesTableRow || insertsOnlyTableRows) {
443
+ if (!referencesTableRow || !insertsOnlyTableRows || !$isTableNode(referenceNode.getParent())) {
444
+ logger.error("❌ Table rows can only be inserted next to another row in the same table.");
445
+ return;
446
+ }
447
+ if (isBefore) newNodes.reverse().forEach((node) => {
448
+ if (!$isTableRowNode(node)) return;
449
+ const diffRow = $createTableRowDiffFromRow(editor, node, "add");
450
+ referenceNode = referenceNode.insertBefore(diffRow);
451
+ });
452
+ else newNodes.forEach((node) => {
453
+ if (!$isTableRowNode(node)) return;
454
+ const diffRow = $createTableRowDiffFromRow(editor, node, "add");
455
+ referenceNode = referenceNode.insertAfter(diffRow);
456
+ });
457
+ return;
458
+ }
278
459
  if (isBefore) {
279
460
  if (referenceNode.isInline() === false) {
280
461
  const originDiffNode = $closest(referenceNode, (node) => node.getType() === DiffNode.getType());