@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
@@ -0,0 +1,68 @@
1
+ import { __esmMin } from "../../../_virtual/_rolldown/runtime.js";
2
+ import { $getEditor, $isRootNode } from "lexical";
3
+ //#region src/plugins/common/node/node-identity-policy.ts
4
+ function subscribeNodeIdentityPolicy(editor, listener) {
5
+ const listeners = policyListeners.get(editor) || /* @__PURE__ */ new Set();
6
+ listeners.add(listener);
7
+ policyListeners.set(editor, listeners);
8
+ let active = true;
9
+ return () => {
10
+ if (!active) return;
11
+ active = false;
12
+ listeners.delete(listener);
13
+ if (listeners.size === 0 && policyListeners.get(editor) === listeners) policyListeners.delete(editor);
14
+ };
15
+ }
16
+ function getNodeIdentityPolicyRevision(editor) {
17
+ return policyRevisions.get(editor) || 0;
18
+ }
19
+ function bumpPolicyRevision(editor) {
20
+ policyRevisions.set(editor, getNodeIdentityPolicyRevision(editor) + 1);
21
+ const listeners = Array.from(policyListeners.get(editor) || []);
22
+ for (const listener of listeners) listener();
23
+ }
24
+ /** Register a document-format policy for one editor, with order-independent disposal. */
25
+ function registerNodeIdentityPolicy(editor, policy) {
26
+ const entry = { policy };
27
+ const registrations = editorPolicies.get(editor) || [];
28
+ registrations.push(entry);
29
+ editorPolicies.set(editor, registrations);
30
+ bumpPolicyRevision(editor);
31
+ return () => {
32
+ const current = editorPolicies.get(editor);
33
+ if (!current) return;
34
+ const index = current.indexOf(entry);
35
+ if (index >= 0) {
36
+ current.splice(index, 1);
37
+ bumpPolicyRevision(editor);
38
+ }
39
+ if (current.length === 0) editorPolicies.delete(editor);
40
+ };
41
+ }
42
+ /**
43
+ * Requires an editor-bound Lexical context. For an EditorState snapshot, pass
44
+ * `{ editor }` to `state.read` so its document policy is unambiguous.
45
+ */
46
+ function $getNodeIdentityPolicy() {
47
+ let editor;
48
+ try {
49
+ editor = $getEditor();
50
+ } catch {
51
+ throw new Error("Node ID access requires editor.read/update or state.read(callback, { editor }).");
52
+ }
53
+ return editorPolicies.get(editor)?.at(-1)?.policy || defaultPolicy;
54
+ }
55
+ var defaultPolicy, editorPolicies, policyRevisions, policyListeners;
56
+ var init_node_identity_policy = __esmMin((() => {
57
+ defaultPolicy = {
58
+ canShareId: () => false,
59
+ isIdentityNode: (node) => !$isRootNode(node),
60
+ project: (node) => $isRootNode(node) ? "container" : "content"
61
+ };
62
+ editorPolicies = /* @__PURE__ */ new WeakMap();
63
+ policyRevisions = /* @__PURE__ */ new WeakMap();
64
+ policyListeners = /* @__PURE__ */ new WeakMap();
65
+ }));
66
+ //#endregion
67
+ init_node_identity_policy();
68
+ export { $getNodeIdentityPolicy, getNodeIdentityPolicyRevision, init_node_identity_policy, registerNodeIdentityPolicy, subscribeNodeIdentityPolicy };
@@ -1,8 +1,11 @@
1
1
  import { INodeHelper, init_helper } from "../../../editor-kernel/inode/helper.js";
2
+ import { $clearNodeId, $normalizeNodeIds, init_node_id } from "../node/node-id.js";
2
3
  import { $isCursorInQuote, $isCursorInTable, createBlockNode, init_utils, sampleReader } from "../utils/index.js";
3
4
  import { KernelPlugin, init_plugin } from "../../../editor-kernel/plugin.js";
4
5
  import { registerCommands } from "../command/index.js";
5
6
  import { CursorNode, registerCursorNode } from "../node/cursor.js";
7
+ import { INodeIdentityService } from "../service/i-node-identity-service.js";
8
+ import { NodeIdentityService } from "../service/node-identity-service.js";
6
9
  import { ILitexmlService } from "../../litexml/service/litexml-service.js";
7
10
  import { isPunctuationChar } from "../../markdown/utils/index.js";
8
11
  import { IMarkdownShortCutService } from "../../markdown/service/shortcut.js";
@@ -14,13 +17,14 @@ import { handleFilePaste, handlePlainTextPaste, handleVSCodePaste, runPasteHandl
14
17
  import { registerProgressiveSelectAll } from "./progressive-select-all.js";
15
18
  import { registerBlockBackspace, registerLastElement, registerRichKeydown } from "./register.js";
16
19
  import { registerHistory } from "@lexical/history";
17
- import { $createLineBreakNode, $createParagraphNode, $getSelection, $isRangeSelection, $isTextNode, COMMAND_PRIORITY_CRITICAL, COMMAND_PRIORITY_HIGH, INSERT_LINE_BREAK_COMMAND, INSERT_PARAGRAPH_COMMAND, PASTE_COMMAND, ParagraphNode, TEXT_TYPE_TO_FORMAT, TextNode } from "lexical";
20
+ import { $createLineBreakNode, $createParagraphNode, $getSelection, $isElementNode, $isRangeSelection, $isTextNode, COMMAND_PRIORITY_CRITICAL, COMMAND_PRIORITY_HIGH, INSERT_LINE_BREAK_COMMAND, INSERT_PARAGRAPH_COMMAND, PASTE_COMMAND, ParagraphNode, RootNode, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, TEXT_TYPE_TO_FORMAT, TextNode } from "lexical";
18
21
  import { $createHeadingNode, $createQuoteNode, $isHeadingNode, $isQuoteNode, HeadingNode, QuoteNode, registerRichText } from "@lexical/rich-text";
19
22
  import { CAN_USE_DOM } from "@lexical/utils";
20
23
  import { registerDragonSupport } from "@lexical/dragon";
21
24
  //#region src/plugins/common/plugin/index.ts
22
25
  init_helper();
23
26
  init_plugin();
27
+ init_node_id();
24
28
  init_utils();
25
29
  const CommonPlugin = class extends KernelPlugin {
26
30
  static {
@@ -30,6 +34,7 @@ const CommonPlugin = class extends KernelPlugin {
30
34
  super();
31
35
  this.kernel = kernel;
32
36
  this.config = config;
37
+ this.identityService = new NodeIdentityService();
33
38
  this.formats = {
34
39
  bold: true,
35
40
  header: true,
@@ -39,6 +44,7 @@ const CommonPlugin = class extends KernelPlugin {
39
44
  subscript: false,
40
45
  superscript: false
41
46
  };
47
+ kernel.registerService(INodeIdentityService, this.identityService);
42
48
  const markdownOption = config.markdownOption ?? true;
43
49
  if (typeof markdownOption === "object") {
44
50
  this.formats.bold = markdownOption.bold ?? true;
@@ -179,6 +185,15 @@ const CommonPlugin = class extends KernelPlugin {
179
185
  });
180
186
  markdownService.registerMarkdownWriter("text", (ctx, node) => {
181
187
  if (!$isTextNode(node)) return;
188
+ let parentNode = node.getParent();
189
+ let isInTableCell = false;
190
+ while (parentNode) {
191
+ if (parentNode.getType() === "tablecell" || parentNode.getType() === "table-cell-diff") {
192
+ isInTableCell = true;
193
+ break;
194
+ }
195
+ parentNode = parentNode.getParent();
196
+ }
182
197
  const isBold = formats.bold && node.hasFormat("bold");
183
198
  const isItalic = formats.italic && node.hasFormat("italic");
184
199
  const isUnderline = node.hasFormat("underline");
@@ -197,7 +212,8 @@ const CommonPlugin = class extends KernelPlugin {
197
212
  if (res) tailSpace = res[0];
198
213
  const append = textContent.trimEnd();
199
214
  const lastChar = append.at(-1);
200
- ctx.appendLine(append);
215
+ const markdownText = isInTableCell ? append.replaceAll("\\", "\\\\").replaceAll("|", "\\|") : append;
216
+ ctx.appendLine(markdownText);
201
217
  if (isSubscript) ctx.appendLine("~");
202
218
  if (isSuperscript) ctx.appendLine("^");
203
219
  if (isUnderline) ctx.appendLine("</ins>");
@@ -213,6 +229,16 @@ const CommonPlugin = class extends KernelPlugin {
213
229
  registerMDReader(markdownService);
214
230
  }
215
231
  onInit(editor) {
232
+ this.register(this.identityService.bindEditor(editor));
233
+ this.register(editor.registerNodeTransform(RootNode, $normalizeNodeIds));
234
+ this.register(editor.registerCommand(SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, ({ nodes }) => {
235
+ const clearCopiedIds = (node) => {
236
+ $clearNodeId(node);
237
+ if ($isElementNode(node)) node.getChildren().forEach(clearCopiedIds);
238
+ };
239
+ nodes.forEach(clearCopiedIds);
240
+ return false;
241
+ }, COMMAND_PRIORITY_CRITICAL));
216
242
  this.register(this.kernel.registerHighCommand(PASTE_COMMAND, (event) => {
217
243
  if (!(event instanceof ClipboardEvent)) return false;
218
244
  const clipboardData = event.clipboardData;
@@ -0,0 +1,19 @@
1
+ import { IServiceID } from "../../../types/kernel.js";
2
+ import "../../../types/index.js";
3
+ //#region src/plugins/common/service/i-node-identity-service.d.ts
4
+ /** A detached, immutable description of the currently active logical node. */
5
+ interface NodeIdentitySnapshot {
6
+ readonly id: string;
7
+ /** Native Lexical text; a review container can include both pending sides. */
8
+ readonly textContent: string;
9
+ readonly type: string;
10
+ }
11
+ interface INodeIdentityService {
12
+ /** Read the last committed editor state without retaining a LexicalNode. */
13
+ getNodeById(nodeId: string): Readonly<NodeIdentitySnapshot> | null;
14
+ /** Subscribe to committed updates; query the service in the callback. */
15
+ subscribe(listener: () => void): () => void;
16
+ }
17
+ declare const INodeIdentityService: IServiceID<INodeIdentityService>;
18
+ //#endregion
19
+ export { INodeIdentityService, NodeIdentitySnapshot };
@@ -0,0 +1,4 @@
1
+ //#region src/plugins/common/service/i-node-identity-service.ts
2
+ const INodeIdentityService = { __serviceId: "NodeIdentityService" };
3
+ //#endregion
4
+ export { INodeIdentityService };
@@ -0,0 +1,87 @@
1
+ import { createDebugLogger, init_debug } from "../../../utils/debug.js";
2
+ import { getNodeIdentityPolicyRevision, init_node_identity_policy, subscribeNodeIdentityPolicy } from "../node/node-identity-policy.js";
3
+ import { $getActiveNodeIdKeys, $getNodeId, init_node_id } from "../node/node-id.js";
4
+ import { $getNodeByKey } from "lexical";
5
+ //#region src/plugins/common/service/node-identity-service.ts
6
+ init_node_id();
7
+ init_node_identity_policy();
8
+ init_debug();
9
+ const logger = createDebugLogger("service", "node-identity");
10
+ /** Implementation owned by CommonPlugin; external callers use INodeIdentityService. */
11
+ var NodeIdentityService = class {
12
+ constructor() {
13
+ this.editor = null;
14
+ this.indexes = /* @__PURE__ */ new WeakMap();
15
+ this.listeners = /* @__PURE__ */ new Set();
16
+ this.bindingToken = null;
17
+ this.unregisterBinding = null;
18
+ }
19
+ bindEditor(editor) {
20
+ this.unregisterBinding?.();
21
+ const bindingToken = {};
22
+ this.bindingToken = bindingToken;
23
+ this.indexes = /* @__PURE__ */ new WeakMap();
24
+ this.editor = editor;
25
+ const unregisterUpdate = editor.registerUpdateListener(({ editorState, prevEditorState }) => {
26
+ if (editorState !== prevEditorState) this.notify();
27
+ });
28
+ const unregisterPolicy = subscribeNodeIdentityPolicy(editor, () => this.notify());
29
+ let active = true;
30
+ const unregister = () => {
31
+ if (!active) return;
32
+ active = false;
33
+ unregisterUpdate();
34
+ unregisterPolicy();
35
+ };
36
+ this.unregisterBinding = unregister;
37
+ return () => {
38
+ unregister();
39
+ if (this.bindingToken !== bindingToken) return;
40
+ this.bindingToken = null;
41
+ this.unregisterBinding = null;
42
+ this.editor = null;
43
+ this.indexes = /* @__PURE__ */ new WeakMap();
44
+ this.listeners.clear();
45
+ };
46
+ }
47
+ getNodeById(nodeId) {
48
+ if (typeof nodeId !== "string" || !nodeId.trim()) return null;
49
+ const editor = this.editor;
50
+ if (!editor) return null;
51
+ const editorState = editor.getEditorState();
52
+ const revision = getNodeIdentityPolicyRevision(editor);
53
+ let index = this.indexes.get(editorState);
54
+ if (!index || index.revision !== revision) {
55
+ index = {
56
+ keys: editorState.read(() => $getActiveNodeIdKeys(), { editor }),
57
+ revision
58
+ };
59
+ this.indexes.set(editorState, index);
60
+ }
61
+ const key = index.keys.get(nodeId);
62
+ if (!key) return null;
63
+ return editorState.read(() => {
64
+ const node = $getNodeByKey(key);
65
+ const id = node && $getNodeId(node);
66
+ return node && id === nodeId ? Object.freeze({
67
+ id,
68
+ textContent: node.getTextContent(),
69
+ type: node.getType()
70
+ }) : null;
71
+ }, { editor });
72
+ }
73
+ subscribe(listener) {
74
+ this.listeners.add(listener);
75
+ return () => this.listeners.delete(listener);
76
+ }
77
+ notify() {
78
+ const listeners = Array.from(this.listeners);
79
+ for (const listener of listeners) try {
80
+ listener();
81
+ } catch (error) {
82
+ logger.error("Node identity subscriber failed:", error);
83
+ }
84
+ }
85
+ };
86
+ //#endregion
87
+ export { NodeIdentityService };
@@ -1,5 +1,6 @@
1
1
  import { __esmMin } from "../../../_virtual/_rolldown/runtime.js";
2
2
  import { INodeHelper, init_helper } from "../../../editor-kernel/inode/helper.js";
3
+ import { $getNodeId, init_node_id } from "../node/node-id.js";
3
4
  import { $isTableCellNode, $isTableNode } from "@lexical/table";
4
5
  import { $getRoot, $isDecoratorNode, $isElementNode, $isParagraphNode, $isRangeSelection, $isTextNode } from "lexical";
5
6
  import { $isQuoteNode } from "@lexical/rich-text";
@@ -99,7 +100,7 @@ function $isCursorInQuote(selection) {
99
100
  function exportNodeToJSON(node) {
100
101
  const serializedNode = node.exportJSON();
101
102
  const nodeClass = node.constructor;
102
- serializedNode.id = node.getKey();
103
+ serializedNode.id = $getNodeId(node);
103
104
  if (serializedNode.type !== nodeClass.getType()) throw new Error(`LexicalNode: Node ${nodeClass.name} does not match the serialized type. Check if .exportJSON() is implemented and it is returning the correct type.`);
104
105
  if ($isElementNode(node)) {
105
106
  const serializedChildren = serializedNode.children;
@@ -115,6 +116,7 @@ function exportNodeToJSON(node) {
115
116
  var sampleReader, createBlockNode;
116
117
  var init_utils = __esmMin((() => {
117
118
  init_helper();
119
+ init_node_id();
118
120
  sampleReader = (format, xmlElement, children) => {
119
121
  children.forEach((child) => {
120
122
  if (INodeHelper.isTextNode(child)) child.format = (child.format || 0) | format;
@@ -62,7 +62,11 @@ const ListPlugin = class extends KernelPlugin {
62
62
  return false;
63
63
  });
64
64
  litexmlService.registerXMLWriter(ListItemNode.getType(), (node, ctx) => {
65
- if ($isListItemNode(node)) return ctx.createXmlNode("li");
65
+ if ($isListItemNode(node)) {
66
+ const child = node.getFirstChild();
67
+ if (child?.getType() === "diff" && "diffType" in child && child.diffType === "listItemRemove") return { lines: [] };
68
+ return ctx.createXmlNode("li");
69
+ }
66
70
  return false;
67
71
  });
68
72
  litexmlService.registerXMLReader("ol", (xmlNode, children) => {
@@ -136,6 +140,8 @@ const ListPlugin = class extends KernelPlugin {
136
140
  markdownService.registerMarkdownWriter(ListItemNode.getType(), (ctx, node) => {
137
141
  const parent = node.getParent();
138
142
  if ($isListItemNode(node) && $isListNode(parent)) {
143
+ const child = node.getFirstChild();
144
+ if (child?.getType() === "diff" && "diffType" in child && child.diffType === "listItemRemove") return true;
139
145
  if ($isListNode(node.getFirstChild())) return;
140
146
  const level = getLevel(parent);
141
147
  const prefix = " ".repeat(level);
@@ -1,4 +1,4 @@
1
1
  import "../data-source/litexml-data-source.js";
2
- import { LITEXML_APPLY_COMMAND, LITEXML_INSERT_COMMAND, LITEXML_MODIFY_COMMAND, LITEXML_REMOVE_COMMAND } from "./symbols.js";
2
+ import { LITEXML_APPLY_COMMAND, LITEXML_INSERT_COMMAND, LITEXML_MODIFY_COMMAND, LITEXML_MODIFY_WITH_RESULTS_COMMAND, LITEXML_REMOVE_COMMAND, LiteXmlModifyOperation, LiteXmlOperationResult } from "./symbols.js";
3
3
  import { LexicalEditor } from "lexical";
4
- export { LITEXML_APPLY_COMMAND, LITEXML_INSERT_COMMAND, LITEXML_MODIFY_COMMAND, LITEXML_REMOVE_COMMAND };
4
+ export { LITEXML_APPLY_COMMAND, LITEXML_INSERT_COMMAND, LITEXML_MODIFY_COMMAND, LITEXML_MODIFY_WITH_RESULTS_COMMAND, LITEXML_REMOVE_COMMAND, type LiteXmlModifyOperation, type LiteXmlOperationResult };