@lobehub/editor 4.29.0 → 4.29.2

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 (48) hide show
  1. package/README.md +86 -0
  2. package/es/editor-kernel/index.d.ts +2 -2
  3. package/es/editor-kernel/index.js +2 -2
  4. package/es/headless/index.d.ts +5 -2
  5. package/es/headless/index.js +20 -22
  6. package/es/headless.d.ts +6 -507
  7. package/es/headless.js +906 -411
  8. package/es/i-node-identity-service-BBC_bg97.d.ts +522 -0
  9. package/es/index.d.ts +4 -2
  10. package/es/index.js +5 -2
  11. package/es/plugins/block/react/ReactBlockPlugin.js +0 -3
  12. package/es/plugins/common/data-source/json-data-source.js +33 -25
  13. package/es/plugins/common/data-source/text-data-source.js +6 -1
  14. package/es/plugins/common/index.d.ts +3 -1
  15. package/es/plugins/common/node/node-id.d.ts +7 -0
  16. package/es/plugins/common/node/node-id.js +484 -0
  17. package/es/plugins/common/node/node-identity-policy.d.ts +26 -0
  18. package/es/plugins/common/node/node-identity-policy.js +68 -0
  19. package/es/plugins/common/plugin/index.js +28 -2
  20. package/es/plugins/common/service/i-node-identity-service.d.ts +19 -0
  21. package/es/plugins/common/service/i-node-identity-service.js +4 -0
  22. package/es/plugins/common/service/node-identity-service.js +87 -0
  23. package/es/plugins/common/utils/index.js +3 -1
  24. package/es/plugins/list/plugin/index.js +7 -1
  25. package/es/plugins/litexml/command/index.d.ts +2 -2
  26. package/es/plugins/litexml/command/index.js +464 -292
  27. package/es/plugins/litexml/command/symbols.d.ts +18 -7
  28. package/es/plugins/litexml/command/symbols.js +2 -1
  29. package/es/plugins/litexml/data-source/litexml-data-source.js +35 -32
  30. package/es/plugins/litexml/diff-validation.js +68 -14
  31. package/es/plugins/litexml/index.d.ts +2 -2
  32. package/es/plugins/litexml/node/identity-policy.js +91 -0
  33. package/es/plugins/litexml/plugin/index.js +6 -0
  34. package/es/plugins/litexml/table-cell-diff.js +4 -0
  35. package/es/plugins/litexml/table-row-diff.js +4 -0
  36. package/es/plugins/litexml/utils/index.js +10 -23
  37. package/es/plugins/markdown/command/index.js +77 -4
  38. package/es/plugins/markdown/data-source/markdown-data-source.js +6 -1
  39. package/es/plugins/math/plugin/index.js +2 -2
  40. package/es/plugins/slash/service/i-slash-service.d.ts +2 -2
  41. package/es/plugins/yjs/plugin/index.js +5 -3
  42. package/es/plugins/yjs/plugin/utils/editor-state.js +8 -1
  43. package/es/plugins/yjs/plugin/utils/node-identity.js +106 -0
  44. package/es/plugins/yjs/service/index.js +38 -4
  45. package/es/{symbols-Do7jD4Tt.d.ts → symbols-Dyr9yyS0.d.ts} +18 -7
  46. package/package.json +4 -4
  47. package/scripts/postinstall-lexical-patch.cjs +0 -21
  48. package/patches/lexical@0.42.0.patch +0 -82
@@ -1,9 +1,11 @@
1
1
  import { createDebugLogger, init_debug } from "../../../utils/debug.js";
2
+ import { $findNodeById, $getNodeId, init_node_id } from "../../common/node/node-id.js";
2
3
  import { parseMarkdownToLexical } from "../data-source/markdown/parse.js";
3
4
  import { $generateNodesFromSerializedNodes, $insertGeneratedNodes } from "../utils/index.js";
4
- import { $getSelection, $isRangeSelection, COMMAND_PRIORITY_HIGH, HISTORIC_TAG, HISTORY_PUSH_TAG, createCommand } from "lexical";
5
+ import { $getNodeByKey, $getRoot, $getSelection, $isElementNode, $isRangeSelection, $isRootNode, $isTextNode, COMMAND_PRIORITY_HIGH, HISTORIC_TAG, HISTORY_PUSH_TAG, createCommand } from "lexical";
5
6
  import { mergeRegister } from "@lexical/utils";
6
7
  //#region src/plugins/markdown/command/index.ts
8
+ init_node_id();
7
9
  init_debug();
8
10
  const logger = createDebugLogger("plugin", "markdown");
9
11
  const INSERT_MARKDOWN_COMMAND = createCommand("INSERT_MARKDOWN_COMMAND");
@@ -16,6 +18,74 @@ const SPICAL_TEXT = "￰";
16
18
  const getLineNumber = (content, charIndex) => {
17
19
  return content.slice(0, Math.max(0, charIndex)).split("\n").length;
18
20
  };
21
+ function getNodeReferenceForKey(editor, key) {
22
+ return editor.getEditorState().read(() => {
23
+ const node = $getNodeByKey(key);
24
+ if (!node) return null;
25
+ if ($isRootNode(node)) return { kind: "root" };
26
+ const id = $getNodeId(node);
27
+ return id ? {
28
+ id,
29
+ kind: "node"
30
+ } : null;
31
+ });
32
+ }
33
+ function getNodeKeyForReference(editor, reference, offset) {
34
+ return editor.getEditorState().read(() => {
35
+ if (reference.kind === "node") {
36
+ const node = $findNodeById(reference.id);
37
+ return node ? {
38
+ key: node.getKey(),
39
+ offset
40
+ } : void 0;
41
+ }
42
+ const root = $getRoot();
43
+ const findBoundaryText = (node, first) => {
44
+ if ($isTextNode(node)) return node;
45
+ if (!$isElementNode(node)) return null;
46
+ const children = node.getChildren();
47
+ const orderedChildren = first ? children : [...children].reverse();
48
+ for (const child of orderedChildren) {
49
+ const textNode = findBoundaryText(child, first);
50
+ if (textNode) return textNode;
51
+ }
52
+ return null;
53
+ };
54
+ if (offset === 0) {
55
+ const firstText = findBoundaryText(root, true);
56
+ if (firstText) return {
57
+ key: firstText.getKey(),
58
+ offset: 0
59
+ };
60
+ }
61
+ if (offset === root.getChildrenSize()) {
62
+ const lastText = findBoundaryText(root, false);
63
+ if (lastText) return {
64
+ key: lastText.getKey(),
65
+ offset: lastText.getTextContentSize()
66
+ };
67
+ }
68
+ return {
69
+ key: root.getKey(),
70
+ offset
71
+ };
72
+ }, { editor });
73
+ }
74
+ function mapSelectionToEditor(selection, sourceEditor, targetEditor) {
75
+ const startReference = getNodeReferenceForKey(sourceEditor, selection.startNodeId);
76
+ const endReference = getNodeReferenceForKey(sourceEditor, selection.endNodeId);
77
+ if (!startReference || !endReference) return null;
78
+ const startPoint = getNodeKeyForReference(targetEditor, startReference, selection.startOffset);
79
+ const endPoint = getNodeKeyForReference(targetEditor, endReference, selection.endOffset);
80
+ if (!startPoint || !endPoint) return null;
81
+ return {
82
+ ...selection,
83
+ endNodeId: endPoint.key,
84
+ endOffset: endPoint.offset,
85
+ startNodeId: startPoint.key,
86
+ startOffset: startPoint.offset
87
+ };
88
+ }
19
89
  function registerMarkdownCommand(editor, kernel, service) {
20
90
  return mergeRegister(editor.registerCommand(INSERT_MARKDOWN_COMMAND, (payload) => {
21
91
  const { markdown } = payload;
@@ -38,9 +108,12 @@ function registerMarkdownCommand(editor, kernel, service) {
38
108
  return false;
39
109
  }, COMMAND_PRIORITY_HIGH), editor.registerCommand(GET_MARKDOWN_SELECTION_COMMAND, (payload) => {
40
110
  const newEditor = kernel.cloneNodeEditor();
41
- const s = kernel.getSelection();
42
- if (s) {
43
- newEditor.setSelection(s);
111
+ const sourceEditor = kernel.getLexicalEditor();
112
+ const selection = kernel.getSelection();
113
+ const targetEditor = newEditor.getLexicalEditor();
114
+ const mappedSelection = sourceEditor && targetEditor && selection ? mapSelectionToEditor(selection, sourceEditor, targetEditor) : null;
115
+ if (mappedSelection && targetEditor) {
116
+ newEditor.setSelection(mappedSelection);
44
117
  newEditor.getLexicalEditor()?.update(() => {
45
118
  const sel = $getSelection();
46
119
  if (!sel) return;
@@ -1,4 +1,5 @@
1
1
  import { INodeHelper, init_helper } from "../../../editor-kernel/inode/helper.js";
2
+ import { $normalizeNodeIds, init_node_id } from "../../common/node/node-id.js";
2
3
  import DataSource from "../../../editor-kernel/data-source.js";
3
4
  import { INodeService } from "../../inode/service/index.js";
4
5
  import { logger } from "../utils/logger.js";
@@ -10,6 +11,7 @@ import { remark } from "remark";
10
11
  import remarkGfm from "remark-gfm";
11
12
  //#region src/plugins/markdown/data-source/markdown-data-source.ts
12
13
  init_helper();
14
+ init_node_id();
13
15
  var MarkdownDataSource = class extends DataSource {
14
16
  formatMarkdown(markdown) {
15
17
  try {
@@ -37,7 +39,10 @@ var MarkdownDataSource = class extends DataSource {
37
39
  const inode = { root: parseMarkdownToLexical(data, this.markdownService.markdownReaders) };
38
40
  this.getService?.(INodeService)?.processNodeTree(inode);
39
41
  logger.debug("Parsed Lexical State:", inode);
40
- editor.setEditorState(editor.parseEditorState(inode));
42
+ const editorState = editor.parseEditorState(inode, () => {
43
+ $normalizeNodeIds($getRoot());
44
+ });
45
+ editor.setEditorState(editorState);
41
46
  }
42
47
  write(editor, options) {
43
48
  if (options?.selection) return editor.getEditorState().read(() => {
@@ -39,14 +39,14 @@ const MathPlugin = class extends KernelPlugin {
39
39
  if (!litexmlService) return;
40
40
  litexmlService.registerXMLWriter(MathInlineNode.getType(), (node, ctx) => {
41
41
  if (node instanceof MathInlineNode) {
42
- const attributes = { code: node.getTextContent() };
42
+ const attributes = { code: node.code };
43
43
  return ctx.createXmlNode("math", attributes);
44
44
  }
45
45
  return false;
46
46
  });
47
47
  litexmlService.registerXMLWriter(MathBlockNode.getType(), (node, ctx) => {
48
48
  if (node instanceof MathBlockNode) {
49
- const attributes = { code: node.getTextContent() };
49
+ const attributes = { code: node.code };
50
50
  return ctx.createXmlNode("mathBlock", attributes);
51
51
  }
52
52
  return false;
@@ -2,13 +2,13 @@ import { IEditor, IServiceID } from "../../../types/kernel.js";
2
2
  import "../../../types/index.js";
3
3
  import "../utils/utils.js";
4
4
  import { ReactNode } from "react";
5
- import { DropdownMenuItemType } from "@lobehub/ui";
5
+ import { MenuItemType } from "@lobehub/ui";
6
6
  import Fuse, { IFuseOptions } from "fuse.js";
7
7
  //#region src/plugins/slash/service/i-slash-service.d.ts
8
8
  type ISlashDividerOption = {
9
9
  type: 'divider';
10
10
  };
11
- interface ISlashMenuOption extends DropdownMenuItemType {
11
+ interface ISlashMenuOption extends MenuItemType {
12
12
  description?: ReactNode;
13
13
  layout?: 'compact' | 'tile' | 'wide';
14
14
  metadata?: Record<string, any>;
@@ -5,6 +5,7 @@ import { hydrateLexicalFromYjsState, syncCurrentEditorStateToYjs } from "./utils
5
5
  import { IYjsService, YjsService } from "../service/index.js";
6
6
  import { getAwarenessUsers } from "./utils/awareness.js";
7
7
  import { registerYjsHistory } from "./utils/history.js";
8
+ import { isNodeIdReconciliationOrigin, reconcileSharedNodeIds } from "./utils/node-identity.js";
8
9
  import { $getRoot, COLLABORATION_TAG, COMMAND_PRIORITY_CRITICAL, SKIP_COLLAB_TAG } from "lexical";
9
10
  import { UndoManager } from "yjs";
10
11
  import { CONNECTED_COMMAND, TOGGLE_CONNECT_COMMAND, createBinding, syncLexicalUpdateToYjs, syncYjsChangesToLexical } from "@lexical/yjs";
@@ -89,7 +90,8 @@ const YjsPlugin = class extends KernelPlugin {
89
90
  return;
90
91
  }
91
92
  if (binding.root.isEmpty() && binding.root._xmlText._length > 0) {
92
- hydrateLexicalFromYjsState(binding);
93
+ hydrateLexicalFromYjsState(binding, { discrete: true });
94
+ reconcileSharedNodeIds(binding);
93
95
  return;
94
96
  }
95
97
  this.bootstrapCurrentEditorState?.();
@@ -122,8 +124,8 @@ const YjsPlugin = class extends KernelPlugin {
122
124
  }
123
125
  registerYjsTreeSync(binding, provider) {
124
126
  const onYjsTreeChanges = (events, transaction) => {
125
- if (transaction.origin === binding) return;
126
- syncYjsChangesToLexical(binding, provider, events, transaction.origin instanceof UndoManager, () => void 0);
127
+ if (transaction.origin === binding || isNodeIdReconciliationOrigin(binding, transaction.origin)) return;
128
+ syncYjsChangesToLexical(binding, provider, events, transaction.origin instanceof UndoManager, () => reconcileSharedNodeIds(binding));
127
129
  };
128
130
  binding.root.getSharedType().observeDeep(onYjsTreeChanges);
129
131
  this.register(() => {
@@ -1,5 +1,7 @@
1
+ import { $normalizeNodeIds, init_node_id, migrateSerializedNodeIds } from "../../../common/node/node-id.js";
1
2
  import { $createParagraphNode, $getRoot, HISTORY_MERGE_TAG, SKIP_COLLAB_TAG } from "lexical";
2
3
  //#region src/plugins/yjs/plugin/utils/editor-state.ts
4
+ init_node_id();
3
5
  function initializeEditor(editor, initialEditorState, options = {}) {
4
6
  const { discrete, skipIfNotEmpty = true, tag = HISTORY_MERGE_TAG } = options;
5
7
  const updateOptions = Object.keys({
@@ -13,7 +15,12 @@ function initializeEditor(editor, initialEditorState, options = {}) {
13
15
  if (editor.getEditorState().read(() => {
14
16
  return skipIfNotEmpty && !$getRoot().isEmpty();
15
17
  })) return;
16
- editor.setEditorState(typeof initialEditorState === "string" ? editor.parseEditorState(initialEditorState) : initialEditorState, updateOptions);
18
+ const serializedState = typeof initialEditorState === "string" ? JSON.parse(initialEditorState) : initialEditorState.toJSON();
19
+ migrateSerializedNodeIds(serializedState.root);
20
+ const parsedState = editor.parseEditorState(serializedState, () => {
21
+ $normalizeNodeIds($getRoot());
22
+ });
23
+ editor.setEditorState(parsedState, updateOptions);
17
24
  return;
18
25
  }
19
26
  editor.update(() => {
@@ -0,0 +1,106 @@
1
+ import { $getNodeIdentityPolicy, init_node_identity_policy } from "../../../common/node/node-identity-policy.js";
2
+ import { $getNodeId, $normalizeNodeIds, init_node_id } from "../../../common/node/node-id.js";
3
+ import { $getRoot, $isElementNode, COLLABORATION_TAG, HISTORIC_TAG } from "lexical";
4
+ import { Map as Map$1, createRelativePositionFromTypeIndex } from "yjs";
5
+ //#region src/plugins/yjs/plugin/utils/node-identity.ts
6
+ init_node_id();
7
+ init_node_identity_policy();
8
+ const reconciliationOrigins = /* @__PURE__ */ new WeakMap();
9
+ function getReconciliationOrigin(binding) {
10
+ let origin = reconciliationOrigins.get(binding);
11
+ if (!origin) {
12
+ origin = { type: "lobe-editor-node-id-reconciliation" };
13
+ reconciliationOrigins.set(binding, origin);
14
+ }
15
+ return origin;
16
+ }
17
+ function isNodeIdReconciliationOrigin(binding, origin) {
18
+ const reconciliationOrigin = reconciliationOrigins.get(binding);
19
+ return reconciliationOrigin !== void 0 && origin === reconciliationOrigin;
20
+ }
21
+ function getSharedNodeState(sharedType) {
22
+ const state = sharedType instanceof Map$1 ? sharedType.get("__state") : sharedType.getAttribute("__state");
23
+ return state instanceof Map$1 ? state : void 0;
24
+ }
25
+ function setSharedNodeState(sharedType, state) {
26
+ if (sharedType instanceof Map$1) sharedType.set("__state", state);
27
+ else sharedType.setAttribute("__state", state);
28
+ }
29
+ function getStableSharedIdentity(binding, node) {
30
+ const sharedType = binding.collabNodeMap.get(node.getKey())?.getSharedType();
31
+ if (!sharedType) return void 0;
32
+ const id = createRelativePositionFromTypeIndex(sharedType, 0).type;
33
+ if (!id) return void 0;
34
+ return `${id.client.toString(36).padStart(7, "0")}:${id.clock.toString(36).padStart(11, "0")}`;
35
+ }
36
+ function hasRepairableSharedIdIssues(binding) {
37
+ const editor = binding.editor;
38
+ return editor.getEditorState().read(() => {
39
+ const policy = $getNodeIdentityPolicy();
40
+ const groups = /* @__PURE__ */ new Map();
41
+ let hasMappedMissingId = false;
42
+ const visit = (node) => {
43
+ if (policy.isIdentityNode(node)) {
44
+ const nodeId = $getNodeId(node);
45
+ if (!nodeId) {
46
+ if (getStableSharedIdentity(binding, node)) hasMappedMissingId = true;
47
+ } else {
48
+ const group = groups.get(nodeId) || [];
49
+ group.push(node);
50
+ groups.set(nodeId, group);
51
+ }
52
+ }
53
+ if ($isElementNode(node)) node.getChildren().forEach(visit);
54
+ };
55
+ visit($getRoot());
56
+ if (hasMappedMissingId) return true;
57
+ for (const group of groups.values()) {
58
+ if (group.length < 2 || group.length === 2 && policy.canShareId(group[0], group[1])) continue;
59
+ const identities = group.map((node) => getStableSharedIdentity(binding, node));
60
+ if (identities.every((identity) => Boolean(identity)) && new Set(identities).size === identities.length) return true;
61
+ }
62
+ return false;
63
+ }, { editor });
64
+ }
65
+ function writeRepairedNodeIds(binding, repairs) {
66
+ if (repairs.length === 0) return;
67
+ binding.doc.transact(() => {
68
+ for (const repair of repairs) {
69
+ const sharedType = binding.collabNodeMap.get(repair.nodeKey)?.getSharedType();
70
+ if (!sharedType) continue;
71
+ const existingState = getSharedNodeState(sharedType);
72
+ const state = existingState ?? new Map$1();
73
+ const currentProperties = existingState?.get("properties");
74
+ const properties = currentProperties && typeof currentProperties === "object" && !Array.isArray(currentProperties) ? currentProperties : {};
75
+ state.set("properties", {
76
+ ...properties,
77
+ nodeId: repair.nodeId
78
+ });
79
+ if (!existingState) setSharedNodeState(sharedType, state);
80
+ }
81
+ }, getReconciliationOrigin(binding));
82
+ }
83
+ /** Repair shared ID collisions only after the binding has mapped their Yjs types. */
84
+ function reconcileSharedNodeIds(binding) {
85
+ if (!hasRepairableSharedIdIssues(binding)) return;
86
+ const repairs = [];
87
+ binding.editor.update(() => {
88
+ $normalizeNodeIds($getRoot(), {
89
+ onRepair: (node, previousId, nodeId) => {
90
+ if (previousId !== nodeId) repairs.push({
91
+ nodeId,
92
+ nodeKey: node.getKey()
93
+ });
94
+ },
95
+ stableDuplicateRepair: true,
96
+ stableOwnershipKey: (node) => getStableSharedIdentity(binding, node)
97
+ });
98
+ }, {
99
+ discrete: true,
100
+ onUpdate: () => writeRepairedNodeIds(binding, repairs),
101
+ skipTransforms: true,
102
+ tag: [COLLABORATION_TAG, HISTORIC_TAG]
103
+ });
104
+ }
105
+ //#endregion
106
+ export { isNodeIdReconciliationOrigin, reconcileSharedNodeIds };
@@ -1,12 +1,40 @@
1
+ import { $normalizeNodeIds, editorStateHasCompleteNodeIds, inheritMissingSerializedNodeIds, init_node_id, isValidContentNodeId, migrateSerializedNodeIds } from "../../common/node/node-id.js";
1
2
  import { createEmptyPreviousEditorState } from "../plugin/utils/editor-state.js";
2
3
  import { hydrateLexicalFromYjsState, syncCurrentEditorStateToYjs } from "../plugin/utils/sync.js";
3
- import { SKIP_COLLAB_TAG } from "lexical";
4
+ import { $getRoot, SKIP_COLLAB_TAG } from "lexical";
4
5
  //#region src/plugins/yjs/service/index.ts
6
+ init_node_id();
5
7
  const serializeComparableEditorState = (state) => {
6
- const serialized = state.toJSON();
8
+ const serialized = structuredClone(state.toJSON());
7
9
  if (serialized.root?.direction === "ltr") serialized.root.direction = null;
8
10
  return JSON.stringify(serialized);
9
11
  };
12
+ const serializeComparableStateForSnapshot = (state, snapshotRoot) => {
13
+ const serialized = structuredClone(state.toJSON());
14
+ if (serialized.root?.direction === "ltr") serialized.root.direction = null;
15
+ const stripMissingIds = (stateNode, snapshotNode) => {
16
+ if (!stateNode || typeof stateNode !== "object" || Array.isArray(stateNode) || !snapshotNode || typeof snapshotNode !== "object" || Array.isArray(snapshotNode)) return;
17
+ const stateRecord = stateNode;
18
+ const snapshotRecord = snapshotNode;
19
+ if (stateRecord.type === snapshotRecord.type) {
20
+ const properties = snapshotRecord.$?.properties;
21
+ if (!(snapshotRecord.type !== "root" && (isValidContentNodeId(snapshotRecord.id) || typeof snapshotRecord.id === "number" && Number.isFinite(snapshotRecord.id) || isValidContentNodeId(properties?.nodeId)))) {
22
+ delete stateRecord.id;
23
+ if (stateRecord.$?.properties) {
24
+ delete stateRecord.$.properties.nodeId;
25
+ if (Object.keys(stateRecord.$.properties).length === 0) delete stateRecord.$.properties;
26
+ if (Object.keys(stateRecord.$).length === 0) delete stateRecord.$;
27
+ }
28
+ }
29
+ if (Array.isArray(stateRecord.children) && Array.isArray(snapshotRecord.children)) {
30
+ const length = Math.min(stateRecord.children.length, snapshotRecord.children.length);
31
+ for (let index = 0; index < length; index++) stripMissingIds(stateRecord.children[index], snapshotRecord.children[index]);
32
+ }
33
+ }
34
+ };
35
+ stripMissingIds(serialized.root, snapshotRoot);
36
+ return JSON.stringify(serialized);
37
+ };
10
38
  var YjsService = class {
11
39
  constructor() {
12
40
  this.awarenessUsers = [];
@@ -40,9 +68,15 @@ var YjsService = class {
40
68
  const hasSharedState = !binding.root.isEmpty() || binding.root._xmlText._length > 0;
41
69
  if (hasSharedState && binding.root.isEmpty()) hydrateLexicalFromYjsState(binding, { discrete: true });
42
70
  const previousEditorState = binding.editor.getEditorState();
43
- const nextEditorState = binding.editor.parseEditorState(JSON.stringify(editorData));
71
+ const snapshot = structuredClone(editorData);
72
+ migrateSerializedNodeIds(snapshot.root);
73
+ inheritMissingSerializedNodeIds(previousEditorState.toJSON().root, snapshot.root);
74
+ const nextEditorState = binding.editor.parseEditorState(JSON.stringify(snapshot), () => {
75
+ $normalizeNodeIds($getRoot(), { stableDuplicateRepair: true });
76
+ });
44
77
  const isSameState = serializeComparableEditorState(previousEditorState) === serializeComparableEditorState(nextEditorState);
45
- if (hasSharedState && isSameState) return false;
78
+ const isSameStateIgnoringOmittedIds = hasSharedState && editorStateHasCompleteNodeIds(previousEditorState, binding.editor) && serializeComparableStateForSnapshot(previousEditorState, snapshot.root) === serializeComparableStateForSnapshot(nextEditorState, snapshot.root);
79
+ if (hasSharedState && (isSameState || isSameStateIgnoringOmittedIds)) return false;
46
80
  binding.editor.setEditorState(nextEditorState, { tag: SKIP_COLLAB_TAG });
47
81
  const replaceSharedState = () => {
48
82
  const collabRoot = binding.root;
@@ -22,21 +22,32 @@ declare enum DiffAction {
22
22
  Reject = 0,
23
23
  Accept = 1
24
24
  }
25
- declare const LITEXML_MODIFY_COMMAND: import("lexical").LexicalCommand<({
26
- action: "insert";
25
+ type LiteXmlModifyOperation = {
26
+ action: 'insert';
27
27
  beforeId: string;
28
28
  litexml: string;
29
29
  } | {
30
- action: "insert";
30
+ action: 'insert';
31
31
  afterId: string;
32
32
  litexml: string;
33
33
  } | {
34
- action: "remove";
34
+ action: 'remove';
35
35
  id: string;
36
36
  } | {
37
- action: "modify";
37
+ action: 'modify';
38
38
  litexml: string | string[];
39
- })[]>;
39
+ };
40
+ interface LiteXmlOperationResult {
41
+ action: LiteXmlModifyOperation['action'];
42
+ index: number;
43
+ reason?: string;
44
+ status: 'applied' | 'failed';
45
+ }
46
+ declare const LITEXML_MODIFY_COMMAND: import("lexical").LexicalCommand<LiteXmlModifyOperation[]>;
47
+ declare const LITEXML_MODIFY_WITH_RESULTS_COMMAND: import("lexical").LexicalCommand<{
48
+ onResults: (results: LiteXmlOperationResult[]) => void;
49
+ operations: ReadonlyArray<LiteXmlModifyOperation>;
50
+ }>;
40
51
  declare const LITEXML_APPLY_COMMAND: import("lexical").LexicalCommand<{
41
52
  delay?: boolean;
42
53
  litexml: string | string[];
@@ -62,4 +73,4 @@ declare const LITEXML_DIFFNODE_ALL_COMMAND: import("lexical").LexicalCommand<{
62
73
  action: DiffAction;
63
74
  }>;
64
75
  //#endregion
65
- export { LITEXML_INSERT_COMMAND as a, LITEXML_DIFFNODE_COMMAND as i, LITEXML_APPLY_COMMAND as n, LITEXML_MODIFY_COMMAND as o, LITEXML_DIFFNODE_ALL_COMMAND as r, LITEXML_REMOVE_COMMAND as s, DiffAction as t };
76
+ export { LITEXML_INSERT_COMMAND as a, LITEXML_REMOVE_COMMAND as c, LITEXML_DIFFNODE_COMMAND as i, LiteXmlModifyOperation as l, LITEXML_APPLY_COMMAND as n, LITEXML_MODIFY_COMMAND as o, LITEXML_DIFFNODE_ALL_COMMAND as r, LITEXML_MODIFY_WITH_RESULTS_COMMAND as s, DiffAction as t, LiteXmlOperationResult as u };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/editor",
3
- "version": "4.29.0",
3
+ "version": "4.29.2",
4
4
  "description": "A powerful and extensible rich text editor built on Meta's Lexical framework, providing a modern editing experience with React integration.",
5
5
  "keywords": [
6
6
  "lobehub",
@@ -86,7 +86,8 @@
86
86
  "test": "vitest",
87
87
  "test:coverage": "vitest run --coverage --passWithNoTests",
88
88
  "test:watch": "vitest --watch",
89
- "type-check": "tsc --noEmit"
89
+ "type-check": "tsc --noEmit",
90
+ "verify:node-identity-package": "node scripts/verify-node-identity-package.mjs"
90
91
  },
91
92
  "dependencies": {
92
93
  "@floating-ui/dom": "^1.8.0",
@@ -143,8 +144,7 @@
143
144
  "chalk": "^4.1.2"
144
145
  },
145
146
  "patchedDependencies": {
146
- "@lexical/yjs@0.42.0": "patches/@lexical__yjs@0.42.0.patch",
147
- "lexical@0.42.0": "patches/lexical@0.42.0.patch"
147
+ "@lexical/yjs@0.42.0": "patches/@lexical__yjs@0.42.0.patch"
148
148
  }
149
149
  }
150
150
  }
@@ -7,27 +7,6 @@ const path = require('node:path');
7
7
 
8
8
  const PACKAGE_ROOT = path.resolve(__dirname, '..');
9
9
  const PATCH_CONFIGS = [
10
- {
11
- displayName: 'Lexical',
12
- fileHashes: {
13
- 'Lexical.dev.js': {
14
- patched: '7c81a9785b397dc09ce0ecc9f3126d3e4903cdfd12d5c51318965ed74b0e3ccb',
15
- },
16
- 'Lexical.dev.mjs': {
17
- patched: '880f22f2ec2d873e1699766de39edce5123b4730009bb88bb33d7e4da98a4ad9',
18
- },
19
- 'Lexical.prod.js': {
20
- patched: '9f97867340b84853cf82bbd2d60ef9f944ee61ef058daa37007b820c2780a103',
21
- },
22
- 'Lexical.prod.mjs': {
23
- patched: 'f7b2993582b2cc0573ca468373831b17971e0bb7383f5ca8785d4b93ab967c0b',
24
- },
25
- },
26
- packageName: 'lexical',
27
- patchFile: path.join(PACKAGE_ROOT, 'patches', 'lexical@0.42.0.patch'),
28
- rootOverride: process.env.LOBE_EDITOR_LEXICAL_ROOT,
29
- supportedVersion: '0.42.0',
30
- },
31
10
  {
32
11
  displayName: 'Lexical Yjs',
33
12
  fileHashes: {