@lobehub/editor 4.24.1 → 4.25.0

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 (143) hide show
  1. package/es/codemirror/style.js +42 -0
  2. package/es/editor-kernel/kernel.js +3 -0
  3. package/es/editor-kernel/react/useDecorators.js +2 -4
  4. package/es/headless/collapsible-plugin.js +74 -0
  5. package/es/headless/index.js +2 -0
  6. package/es/headless.d.ts +18 -0
  7. package/es/headless.js +2479 -71
  8. package/es/index.d.ts +26 -3
  9. package/es/index.js +17 -2
  10. package/es/locale/index.d.ts +14 -0
  11. package/es/locale/index.js +14 -0
  12. package/es/plugins/block/command/index.js +24 -0
  13. package/es/plugins/block/plugin/index.js +7 -5
  14. package/es/plugins/block/react/ReactBlockPlugin.js +43 -1
  15. package/es/plugins/block/react/core/menu-position.js +20 -0
  16. package/es/plugins/block/react/drag/drag-session.js +3 -2
  17. package/es/plugins/block/react/drag/drag-utils.js +13 -1
  18. package/es/plugins/codemirror-block/index.d.ts +2 -1
  19. package/es/plugins/codemirror-block/plugin/index.js +2 -0
  20. package/es/plugins/codemirror-block/react/CodemirrorNode.js +95 -5
  21. package/es/plugins/codemirror-block/react/useCodemirrorEditLock.js +33 -0
  22. package/es/plugins/codemirror-block/service/index.d.ts +40 -0
  23. package/es/plugins/codemirror-block/service/index.js +59 -0
  24. package/es/plugins/collapsible/command/index.d.ts +17 -0
  25. package/es/plugins/collapsible/command/index.js +72 -0
  26. package/es/plugins/collapsible/index.d.ts +7 -0
  27. package/es/plugins/collapsible/node/CollapsibleNode.d.ts +30 -0
  28. package/es/plugins/collapsible/node/CollapsibleNode.js +138 -0
  29. package/es/plugins/collapsible/plugin/index.d.ts +11 -0
  30. package/es/plugins/collapsible/plugin/index.js +535 -0
  31. package/es/plugins/collapsible/react/ReactCollapsiblePlugin.d.ts +6 -0
  32. package/es/plugins/collapsible/react/ReactCollapsiblePlugin.js +19 -0
  33. package/es/plugins/collapsible/react/index.d.ts +3 -0
  34. package/es/plugins/collapsible/react/style.js +67 -0
  35. package/es/plugins/collapsible/react/type.d.ts +6 -0
  36. package/es/plugins/common/plugin/index.js +3 -2
  37. package/es/plugins/common/plugin/progressive-select-all.js +81 -0
  38. package/es/plugins/common/plugin/register.js +73 -4
  39. package/es/plugins/link/command/index.js +26 -3
  40. package/es/plugins/link/conversion/index.js +237 -0
  41. package/es/plugins/link/index.d.ts +5 -3
  42. package/es/plugins/link/node/LinkBlockCardNode.d.ts +16 -0
  43. package/es/plugins/link/node/LinkBlockCardNode.js +66 -0
  44. package/es/plugins/link/node/LinkCardNode.d.ts +42 -0
  45. package/es/plugins/link/node/LinkCardNode.js +161 -0
  46. package/es/plugins/link/node/LinkIframeNode.d.ts +33 -0
  47. package/es/plugins/link/node/LinkIframeNode.js +131 -0
  48. package/es/plugins/link/node/LinkNode.d.ts +48 -2
  49. package/es/plugins/link/node/LinkNode.js +14 -6
  50. package/es/plugins/link/node/SchemaNode.d.ts +36 -0
  51. package/es/plugins/link/node/SchemaNode.js +149 -0
  52. package/es/plugins/link/normalization/index.d.ts +7 -0
  53. package/es/plugins/link/normalization/index.js +38 -0
  54. package/es/plugins/link/plugin/index.d.ts +26 -1
  55. package/es/plugins/link/plugin/index.js +124 -4
  56. package/es/plugins/link/react/ReactLinkPlugin.js +165 -7
  57. package/es/plugins/link/react/components/LinkCard.js +235 -0
  58. package/es/plugins/link/react/components/LinkEdit.js +93 -14
  59. package/es/plugins/link/react/components/LinkIframe.js +128 -0
  60. package/es/plugins/link/react/components/LinkToolbar.js +440 -111
  61. package/es/plugins/link/react/components/SchemaLink.js +49 -0
  62. package/es/plugins/link/react/index.d.ts +2 -2
  63. package/es/plugins/link/react/renderer-registry.d.ts +81 -0
  64. package/es/plugins/link/react/renderer-registry.js +80 -0
  65. package/es/plugins/link/react/style.js +63 -1
  66. package/es/plugins/link/react/type.d.ts +24 -1
  67. package/es/plugins/link/service/i-link-service.d.ts +156 -1
  68. package/es/plugins/link/service/i-link-service.js +258 -5
  69. package/es/plugins/list/plugin/index.js +9 -2
  70. package/es/plugins/litexml/command/diffCommand.js +94 -14
  71. package/es/plugins/litexml/command/index.js +187 -6
  72. package/es/plugins/litexml/diff-validation.js +237 -0
  73. package/es/plugins/litexml/node/DiffContentNode.js +67 -0
  74. package/es/plugins/litexml/node/DiffNode.d.ts +1 -1
  75. package/es/plugins/litexml/node/DiffNode.js +5 -2
  76. package/es/plugins/litexml/node/TableCellDiffNode.js +77 -0
  77. package/es/plugins/litexml/node/TableRowDiffNode.js +73 -0
  78. package/es/plugins/litexml/plugin/index.d.ts +2 -0
  79. package/es/plugins/litexml/plugin/index.js +62 -9
  80. package/es/plugins/litexml/react/DiffNodeToolbar/index.js +10 -12
  81. package/es/plugins/litexml/react/TableRowDiffToolbar.js +123 -0
  82. package/es/plugins/litexml/react/hooks/useHasDiffNode.js +5 -2
  83. package/es/plugins/litexml/react/index.js +6 -2
  84. package/es/plugins/litexml/react/style.js +18 -5
  85. package/es/plugins/litexml/table-cell-diff.js +153 -0
  86. package/es/plugins/litexml/table-row-diff.js +82 -0
  87. package/es/plugins/markdown/data-source/markdown/parse.js +25 -1
  88. package/es/plugins/markdown/plugin/index.js +5 -3
  89. package/es/plugins/slash/react/ReactSlashPlugin.js +22 -43
  90. package/es/plugins/slash/react/components/DefaultSlashMenu.js +76 -16
  91. package/es/plugins/slash/react/menu-utils.js +84 -0
  92. package/es/plugins/slash/service/i-slash-service.d.ts +3 -0
  93. package/es/plugins/table/plugin/index.js +4 -1
  94. package/es/plugins/table/plugin/single-cell-paste.js +79 -0
  95. package/es/plugins/table/react/TableController/hooks.js +7 -4
  96. package/es/plugins/table/react/hooks.js +2 -2
  97. package/es/plugins/table/react/index.js +2 -0
  98. package/es/plugins/table/react/style.js +15 -5
  99. package/es/plugins/table/react/useScrollFocusedTableCellIntoView.js +46 -0
  100. package/es/plugins/toc/index.d.ts +10 -0
  101. package/es/plugins/toc/plugin/index.d.ts +8 -0
  102. package/es/plugins/toc/plugin/index.js +56 -0
  103. package/es/plugins/toc/react/ReactTocPlugin.d.ts +6 -0
  104. package/es/plugins/toc/react/ReactTocPlugin.js +36 -0
  105. package/es/plugins/toc/react/getNearestScrollContainer.d.ts +6 -0
  106. package/es/plugins/toc/react/getNearestScrollContainer.js +18 -0
  107. package/es/plugins/toc/react/hooks/useActiveHeading.js +59 -0
  108. package/es/plugins/toc/react/hooks/useTocItems.js +50 -0
  109. package/es/plugins/toc/react/index.d.ts +5 -0
  110. package/es/plugins/toc/react/type.d.ts +30 -0
  111. package/es/plugins/toc/react/useToc.d.ts +5 -0
  112. package/es/plugins/toc/react/useToc.js +41 -0
  113. package/es/plugins/toc/service/i-toc-service.d.ts +39 -0
  114. package/es/plugins/toc/service/i-toc-service.js +125 -0
  115. package/es/plugins/toc/service/index.d.ts +3 -0
  116. package/es/plugins/toc/types.d.ts +23 -0
  117. package/es/plugins/toc/utils/index.d.ts +6 -0
  118. package/es/plugins/toc/utils/index.js +30 -0
  119. package/es/plugins/toolbar/react/index.js +26 -12
  120. package/es/plugins/toolbar/react/style.js +3 -2
  121. package/es/plugins/virtual-block/plugin/register.js +34 -12
  122. package/es/plugins/yjs/index.d.ts +6 -0
  123. package/es/plugins/yjs/plugin/index.d.ts +7 -0
  124. package/es/plugins/yjs/plugin/index.js +191 -0
  125. package/es/plugins/yjs/plugin/types.d.ts +16 -0
  126. package/es/plugins/yjs/plugin/utils/awareness.js +9 -0
  127. package/es/plugins/yjs/plugin/utils/editor-state.js +47 -0
  128. package/es/plugins/yjs/plugin/utils/history.js +31 -0
  129. package/es/plugins/yjs/plugin/utils/node-properties.js +39 -0
  130. package/es/plugins/yjs/plugin/utils/sync.js +24 -0
  131. package/es/plugins/yjs/react/codemirrorEditLockProvider.js +59 -0
  132. package/es/plugins/yjs/react/index.d.ts +23 -0
  133. package/es/plugins/yjs/react/index.js +200 -0
  134. package/es/plugins/yjs/service/index.d.ts +47 -0
  135. package/es/plugins/yjs/service/index.js +84 -0
  136. package/es/plugins/yjs/websocket-provider.d.ts +78 -0
  137. package/es/plugins/yjs/websocket-provider.js +263 -0
  138. package/es/renderer/LexicalRenderer.js +9 -2
  139. package/es/types/kernel.d.ts +4 -0
  140. package/es/utils/updatePosition.js +5 -2
  141. package/package.json +3 -1
  142. package/patches/@lexical__yjs@0.42.0.patch +42 -0
  143. package/scripts/postinstall-lexical-patch.cjs +62 -36
@@ -0,0 +1,191 @@
1
+ import { KernelPlugin, init_plugin } from "../../../editor-kernel/plugin.js";
2
+ import { clearEditorSkipCollab, initializeEditor } from "./utils/editor-state.js";
3
+ import { ensureYjsNodePropertiesFromEditorState } from "./utils/node-properties.js";
4
+ import { hydrateLexicalFromYjsState, syncCurrentEditorStateToYjs } from "./utils/sync.js";
5
+ import { IYjsService, YjsService } from "../service/index.js";
6
+ import { getAwarenessUsers } from "./utils/awareness.js";
7
+ import { registerYjsHistory } from "./utils/history.js";
8
+ import { $getRoot, COLLABORATION_TAG, COMMAND_PRIORITY_CRITICAL, SKIP_COLLAB_TAG } from "lexical";
9
+ import { UndoManager } from "yjs";
10
+ import { CONNECTED_COMMAND, TOGGLE_CONNECT_COMMAND, createBinding, syncLexicalUpdateToYjs, syncYjsChangesToLexical } from "@lexical/yjs";
11
+ //#region src/plugins/yjs/plugin/index.ts
12
+ init_plugin();
13
+ const YjsPlugin = class extends KernelPlugin {
14
+ static {
15
+ this.pluginName = "YjsPlugin";
16
+ }
17
+ constructor(kernel, config) {
18
+ super();
19
+ this.kernel = kernel;
20
+ this.config = config;
21
+ this.bootstrapCurrentEditorState = null;
22
+ this.docMap = /* @__PURE__ */ new Map();
23
+ this.hasInitialized = false;
24
+ this.isReloadingDoc = false;
25
+ this.markDocumentChanged = null;
26
+ this.service = new YjsService();
27
+ kernel.registerServiceHotReload(IYjsService, this.service);
28
+ }
29
+ destroy() {
30
+ super.destroy();
31
+ this.bootstrapCurrentEditorState = null;
32
+ this.markDocumentChanged = null;
33
+ this.service.setState(null);
34
+ }
35
+ onDocumentChange() {
36
+ this.markDocumentChanged?.();
37
+ }
38
+ connectProvider(provider, connectionState) {
39
+ if (connectionState.hasConnected) return;
40
+ connectionState.hasConnected = true;
41
+ connectionState.connection = provider.connect();
42
+ }
43
+ registerAwareness(provider) {
44
+ this.service.setAwarenessUsers(getAwarenessUsers(provider));
45
+ const updateAwarenessUsers = () => {
46
+ this.service.setAwarenessUsers(getAwarenessUsers(provider));
47
+ };
48
+ provider.awareness.on("update", updateAwarenessUsers);
49
+ this.register(() => {
50
+ provider.awareness.off("update", updateAwarenessUsers);
51
+ this.service.setAwarenessUsers([]);
52
+ });
53
+ }
54
+ registerEditorSync(editor, binding, provider, shouldBootstrap, syncState) {
55
+ this.register(editor.registerUpdateListener(({ dirtyElements, dirtyLeaves, editorState, normalizedNodes, prevEditorState, tags }) => {
56
+ if (tags.has(SKIP_COLLAB_TAG) || tags.has(COLLABORATION_TAG)) return;
57
+ if (!syncState.providerHasSynced) return;
58
+ ensureYjsNodePropertiesFromEditorState(binding, editorState);
59
+ if (shouldBootstrap && syncState.providerHasSynced && binding.root.isEmpty() && binding.root._xmlText._length === 0) {
60
+ if (!editorState.read(() => $getRoot().isEmpty())) {
61
+ syncCurrentEditorStateToYjs(binding, provider);
62
+ return;
63
+ }
64
+ }
65
+ syncLexicalUpdateToYjs(binding, provider, prevEditorState, editorState, dirtyElements, dirtyLeaves, normalizedNodes, tags);
66
+ }));
67
+ }
68
+ registerProviderEvents(editor, binding, provider, id, syncState) {
69
+ const onProviderDocReload = (doc) => {
70
+ clearEditorSkipCollab(editor);
71
+ this.docMap.set(id, doc);
72
+ this.isReloadingDoc = true;
73
+ this.service.setState({
74
+ binding,
75
+ doc,
76
+ docMap: this.docMap,
77
+ id,
78
+ provider
79
+ });
80
+ };
81
+ const onStatus = ({ status }) => {
82
+ editor.dispatchCommand(CONNECTED_COMMAND, status === "connected");
83
+ };
84
+ const onSync = (isSynced) => {
85
+ if (!isSynced) return;
86
+ syncState.providerHasSynced = true;
87
+ if (this.isReloadingDoc) {
88
+ this.isReloadingDoc = false;
89
+ return;
90
+ }
91
+ if (binding.root.isEmpty() && binding.root._xmlText._length > 0) {
92
+ hydrateLexicalFromYjsState(binding);
93
+ return;
94
+ }
95
+ this.bootstrapCurrentEditorState?.();
96
+ };
97
+ provider.on("reload", onProviderDocReload);
98
+ provider.on("status", onStatus);
99
+ provider.on("sync", onSync);
100
+ this.register(() => {
101
+ provider.off("reload", onProviderDocReload);
102
+ provider.off("status", onStatus);
103
+ provider.off("sync", onSync);
104
+ this.bootstrapCurrentEditorState = null;
105
+ this.markDocumentChanged = null;
106
+ });
107
+ }
108
+ registerProviderToggleCommand(editor, provider) {
109
+ this.register(editor.registerCommand(TOGGLE_CONNECT_COMMAND, (shouldConnect) => {
110
+ if (shouldConnect) provider.connect();
111
+ else provider.disconnect();
112
+ return true;
113
+ }, COMMAND_PRIORITY_CRITICAL));
114
+ }
115
+ registerProviderTeardown(binding, provider, connectionState) {
116
+ this.register(() => {
117
+ if (connectionState.connection) connectionState.connection.then(() => provider.disconnect());
118
+ else if (connectionState.hasConnected) provider.disconnect();
119
+ binding.root.destroy(binding);
120
+ this.service.setState(null);
121
+ });
122
+ }
123
+ registerYjsTreeSync(binding, provider) {
124
+ const onYjsTreeChanges = (events, transaction) => {
125
+ if (transaction.origin === binding) return;
126
+ syncYjsChangesToLexical(binding, provider, events, transaction.origin instanceof UndoManager, () => void 0);
127
+ };
128
+ binding.root.getSharedType().observeDeep(onYjsTreeChanges);
129
+ this.register(() => {
130
+ binding.root.getSharedType().unobserveDeep(onYjsTreeChanges);
131
+ });
132
+ }
133
+ setBootstrapCurrentEditorState(editor, binding, provider, initialEditorState, shouldBootstrap, syncState) {
134
+ this.bootstrapCurrentEditorState = () => {
135
+ if (!shouldBootstrap || !syncState.providerHasSynced || !syncState.documentHasChanged && !initialEditorState || !binding.root.isEmpty() || binding.root._xmlText._length !== 0) return;
136
+ if (editor.getEditorState().read(() => $getRoot().isEmpty()) && !initialEditorState) return;
137
+ if (initialEditorState) {
138
+ clearEditorSkipCollab(editor);
139
+ initializeEditor(editor, initialEditorState, {
140
+ discrete: true,
141
+ skipIfNotEmpty: false,
142
+ tag: void 0
143
+ });
144
+ }
145
+ syncCurrentEditorStateToYjs(binding, provider);
146
+ };
147
+ }
148
+ setServiceState(binding, id, provider, doc) {
149
+ this.service.setState({
150
+ binding,
151
+ doc,
152
+ docMap: this.docMap,
153
+ id,
154
+ provider
155
+ });
156
+ }
157
+ onInit(editor) {
158
+ const { excludedProperties, id, initialEditorState, providerFactory, shouldBootstrap = true, yjsDoc } = this.config || {};
159
+ if (!id || !providerFactory) throw new Error("YjsPlugin requires both \"id\" and \"providerFactory\".");
160
+ if (this.hasInitialized) return;
161
+ this.hasInitialized = true;
162
+ if (yjsDoc) this.docMap.set(id, yjsDoc);
163
+ const provider = providerFactory(id, this.docMap);
164
+ const connectionState = {
165
+ connection: void 0,
166
+ hasConnected: false
167
+ };
168
+ const syncState = {
169
+ documentHasChanged: false,
170
+ providerHasSynced: false
171
+ };
172
+ const binding = createBinding(editor, provider, id, this.docMap.get(id), this.docMap, excludedProperties);
173
+ this.setServiceState(binding, id, provider, this.docMap.get(id));
174
+ this.registerAwareness(provider);
175
+ this.registerYjsTreeSync(binding, provider);
176
+ this.registerEditorSync(editor, binding, provider, shouldBootstrap, syncState);
177
+ this.setBootstrapCurrentEditorState(editor, binding, provider, initialEditorState, shouldBootstrap, syncState);
178
+ this.markDocumentChanged = () => {
179
+ syncState.documentHasChanged = true;
180
+ this.connectProvider(provider, connectionState);
181
+ this.bootstrapCurrentEditorState?.();
182
+ };
183
+ this.registerProviderEvents(editor, binding, provider, id, syncState);
184
+ this.registerProviderToggleCommand(editor, provider);
185
+ this.register(registerYjsHistory(editor, binding));
186
+ if (initialEditorState) this.connectProvider(provider, connectionState);
187
+ this.registerProviderTeardown(binding, provider, connectionState);
188
+ }
189
+ };
190
+ //#endregion
191
+ export { YjsPlugin };
@@ -0,0 +1,16 @@
1
+ import { EditorState, LexicalEditor } from "lexical";
2
+ import { Doc } from "yjs";
3
+ import { ExcludedProperties, Provider } from "@lexical/yjs";
4
+ //#region src/plugins/yjs/plugin/types.d.ts
5
+ type YjsProviderFactory = (id: string, yjsDocMap: Map<string, Doc>) => Provider;
6
+ type YjsInitialEditorState = EditorState | ((editor: LexicalEditor) => void) | null | string;
7
+ interface YjsPluginOptions {
8
+ excludedProperties?: ExcludedProperties;
9
+ id: string;
10
+ initialEditorState?: YjsInitialEditorState;
11
+ providerFactory: YjsProviderFactory;
12
+ shouldBootstrap?: boolean;
13
+ yjsDoc?: Doc;
14
+ }
15
+ //#endregion
16
+ export { YjsInitialEditorState, YjsPluginOptions, YjsProviderFactory };
@@ -0,0 +1,9 @@
1
+ //#region src/plugins/yjs/plugin/utils/awareness.ts
2
+ function getAwarenessUsers(provider) {
3
+ return Array.from(provider.awareness.getStates(), ([clientId, state]) => ({
4
+ clientId,
5
+ state
6
+ }));
7
+ }
8
+ //#endregion
9
+ export { getAwarenessUsers };
@@ -0,0 +1,47 @@
1
+ import { $createParagraphNode, $getRoot, HISTORY_MERGE_TAG, SKIP_COLLAB_TAG } from "lexical";
2
+ //#region src/plugins/yjs/plugin/utils/editor-state.ts
3
+ function initializeEditor(editor, initialEditorState, options = {}) {
4
+ const { discrete, skipIfNotEmpty = true, tag = HISTORY_MERGE_TAG } = options;
5
+ const updateOptions = Object.keys({
6
+ ...discrete ? { discrete: true } : {},
7
+ ...tag ? { tag } : {}
8
+ }).length ? {
9
+ ...discrete ? { discrete: true } : {},
10
+ ...tag ? { tag } : {}
11
+ } : void 0;
12
+ if (initialEditorState && typeof initialEditorState !== "function") {
13
+ if (editor.getEditorState().read(() => {
14
+ return skipIfNotEmpty && !$getRoot().isEmpty();
15
+ })) return;
16
+ editor.setEditorState(typeof initialEditorState === "string" ? editor.parseEditorState(initialEditorState) : initialEditorState, updateOptions);
17
+ return;
18
+ }
19
+ editor.update(() => {
20
+ const root = $getRoot();
21
+ if (skipIfNotEmpty && !root.isEmpty()) return;
22
+ if (typeof initialEditorState === "function") {
23
+ initialEditorState(editor);
24
+ return;
25
+ }
26
+ root.append($createParagraphNode());
27
+ }, updateOptions);
28
+ }
29
+ function createEmptyPreviousEditorState(editor) {
30
+ return editor.parseEditorState(JSON.stringify({ root: {
31
+ children: [],
32
+ direction: null,
33
+ format: "",
34
+ indent: 0,
35
+ type: "root",
36
+ version: 1
37
+ } }));
38
+ }
39
+ function clearEditorSkipCollab(editor) {
40
+ editor.update(() => {
41
+ const root = $getRoot();
42
+ root.clear();
43
+ root.select();
44
+ }, { tag: SKIP_COLLAB_TAG });
45
+ }
46
+ //#endregion
47
+ export { clearEditorSkipCollab, createEmptyPreviousEditorState, initializeEditor };
@@ -0,0 +1,31 @@
1
+ import { CAN_REDO_COMMAND, CAN_UNDO_COMMAND, COMMAND_PRIORITY_CRITICAL, REDO_COMMAND, UNDO_COMMAND } from "lexical";
2
+ import { createUndoManager } from "@lexical/yjs";
3
+ //#region src/plugins/yjs/plugin/utils/history.ts
4
+ function registerYjsHistory(editor, binding) {
5
+ const undoManager = createUndoManager(binding, binding.root.getSharedType());
6
+ const updateUndoRedoState = () => {
7
+ editor.dispatchCommand(CAN_UNDO_COMMAND, undoManager.undoStack.length > 0);
8
+ editor.dispatchCommand(CAN_REDO_COMMAND, undoManager.redoStack.length > 0);
9
+ };
10
+ undoManager.on("stack-item-added", updateUndoRedoState);
11
+ undoManager.on("stack-item-popped", updateUndoRedoState);
12
+ undoManager.on("stack-cleared", updateUndoRedoState);
13
+ const unregisterUndo = editor.registerCommand(UNDO_COMMAND, () => {
14
+ undoManager.undo();
15
+ return true;
16
+ }, COMMAND_PRIORITY_CRITICAL);
17
+ const unregisterRedo = editor.registerCommand(REDO_COMMAND, () => {
18
+ undoManager.redo();
19
+ return true;
20
+ }, COMMAND_PRIORITY_CRITICAL);
21
+ return () => {
22
+ unregisterUndo();
23
+ unregisterRedo();
24
+ undoManager.off("stack-item-added", updateUndoRedoState);
25
+ undoManager.off("stack-item-popped", updateUndoRedoState);
26
+ undoManager.off("stack-cleared", updateUndoRedoState);
27
+ undoManager.destroy();
28
+ };
29
+ }
30
+ //#endregion
31
+ export { registerYjsHistory };
@@ -0,0 +1,39 @@
1
+ //#region src/plugins/yjs/plugin/utils/node-properties.ts
2
+ const ignoredNodeProperties = /* @__PURE__ */ new Set([
3
+ "__cachedText",
4
+ "__first",
5
+ "__key",
6
+ "__last",
7
+ "__next",
8
+ "__parent",
9
+ "__prev",
10
+ "__size",
11
+ "__state",
12
+ "__text"
13
+ ]);
14
+ function shouldIgnoreNodeProperty(property, node, binding) {
15
+ if (ignoredNodeProperties.has(property) || typeof node[property] === "function") return true;
16
+ const excludedProperties = binding.excludedProperties.get(node.constructor);
17
+ return Boolean(excludedProperties?.has(property));
18
+ }
19
+ function ensureYjsNodeProperties(binding, node) {
20
+ if (binding.nodeProperties.has(node.__type)) return;
21
+ let defaultNode = node;
22
+ try {
23
+ defaultNode = new node.constructor();
24
+ } catch {
25
+ defaultNode = node;
26
+ }
27
+ const defaultProperties = {};
28
+ for (const [property, value] of Object.entries(defaultNode)) if (!shouldIgnoreNodeProperty(property, defaultNode, binding)) defaultProperties[property] = value;
29
+ binding.nodeProperties.set(node.__type, Object.freeze(defaultProperties));
30
+ }
31
+ function ensureYjsNodePropertiesFromEditorState(binding, editorState) {
32
+ editorState.read(() => {
33
+ editorState._nodeMap.forEach((node) => {
34
+ ensureYjsNodeProperties(binding, node);
35
+ });
36
+ });
37
+ }
38
+ //#endregion
39
+ export { ensureYjsNodePropertiesFromEditorState };
@@ -0,0 +1,24 @@
1
+ import { createEmptyPreviousEditorState } from "./editor-state.js";
2
+ import { ensureYjsNodePropertiesFromEditorState } from "./node-properties.js";
3
+ import { COLLABORATION_TAG } from "lexical";
4
+ import { syncLexicalUpdateToYjs } from "@lexical/yjs";
5
+ //#region src/plugins/yjs/plugin/utils/sync.ts
6
+ function syncCurrentEditorStateToYjs(binding, provider, prevEditorState = createEmptyPreviousEditorState(binding.editor)) {
7
+ const editorState = binding.editor.getEditorState();
8
+ ensureYjsNodePropertiesFromEditorState(binding, editorState);
9
+ syncLexicalUpdateToYjs(binding, provider, prevEditorState, editorState, /* @__PURE__ */ new Map([["root", true]]), /* @__PURE__ */ new Set(), /* @__PURE__ */ new Set(), /* @__PURE__ */ new Set());
10
+ }
11
+ function hydrateLexicalFromYjsState(binding, options = {}) {
12
+ const root = binding.root;
13
+ const sharedType = root.getSharedType();
14
+ binding.editor.update(() => {
15
+ root.applyChildrenYjsDelta(binding, sharedType.toDelta());
16
+ root.syncChildrenFromYjs(binding);
17
+ }, {
18
+ discrete: options.discrete ? true : void 0,
19
+ skipTransforms: true,
20
+ tag: COLLABORATION_TAG
21
+ });
22
+ }
23
+ //#endregion
24
+ export { hydrateLexicalFromYjsState, syncCurrentEditorStateToYjs };
@@ -0,0 +1,59 @@
1
+ //#region src/plugins/yjs/react/codemirrorEditLockProvider.ts
2
+ function getAwarenessData(state) {
3
+ const awarenessData = state?.awarenessData;
4
+ if (!awarenessData || typeof awarenessData !== "object") return {};
5
+ return awarenessData;
6
+ }
7
+ function getEditingBlock(state) {
8
+ const editingBlock = getAwarenessData(state).editingBlock;
9
+ return editingBlock && typeof editingBlock.key === "string" ? editingBlock : null;
10
+ }
11
+ function updateLocalLock(state, key, nextLock) {
12
+ const localState = state.provider.awareness.getLocalState();
13
+ if (!localState) return;
14
+ const nextAwarenessData = { ...getAwarenessData(localState) };
15
+ if (nextLock) nextAwarenessData.editingBlock = nextLock;
16
+ else if (nextAwarenessData.editingBlock?.key === key) delete nextAwarenessData.editingBlock;
17
+ state.provider.awareness.setLocalState({
18
+ ...localState,
19
+ awarenessData: nextAwarenessData
20
+ });
21
+ }
22
+ function createCodemirrorEditLockProvider(state) {
23
+ const localClientId = state.binding.clientID;
24
+ const getRemoteLockOwner = (key) => {
25
+ for (const [clientId, userState] of state.provider.awareness.getStates()) {
26
+ if (clientId === localClientId) continue;
27
+ const lock = getEditingBlock(userState);
28
+ if (lock?.key === key) return {
29
+ id: clientId,
30
+ lock,
31
+ name: userState.name
32
+ };
33
+ }
34
+ return null;
35
+ };
36
+ return {
37
+ acquireLock: (key, label) => {
38
+ if (getRemoteLockOwner(key)) return false;
39
+ updateLocalLock(state, key, {
40
+ key,
41
+ label,
42
+ updatedAt: Date.now()
43
+ });
44
+ return true;
45
+ },
46
+ getRemoteLockOwner,
47
+ releaseLock: (key) => {
48
+ updateLocalLock(state, key, null);
49
+ },
50
+ subscribe: (listener) => {
51
+ state.provider.awareness.on("update", listener);
52
+ return () => {
53
+ state.provider.awareness.off("update", listener);
54
+ };
55
+ }
56
+ };
57
+ }
58
+ //#endregion
59
+ export { createCodemirrorEditLockProvider };
@@ -0,0 +1,23 @@
1
+ import { YjsInitialEditorState, YjsProviderFactory } from "../plugin/types.js";
2
+ import "../plugin/index.js";
3
+ import { FC, RefObject } from "react";
4
+ import { Doc, Doc as Doc$1 } from "yjs";
5
+ import { ExcludedProperties, SyncCursorPositionsFn } from "@lexical/yjs";
6
+ //#region src/plugins/yjs/react/index.d.ts
7
+ interface ReactYjsPluginProps {
8
+ awarenessData?: object;
9
+ cursorColor?: string;
10
+ cursorsContainerRef?: RefObject<HTMLElement | null>;
11
+ excludedProperties?: ExcludedProperties;
12
+ id: string;
13
+ initialEditorState?: YjsInitialEditorState;
14
+ persistCursorOnBlur?: boolean;
15
+ providerFactory: YjsProviderFactory;
16
+ shouldBootstrap?: boolean;
17
+ syncCursorPositionsFn?: SyncCursorPositionsFn;
18
+ username?: string;
19
+ yjsDoc?: Doc;
20
+ }
21
+ declare const ReactYjsPlugin: FC<ReactYjsPluginProps>;
22
+ //#endregion
23
+ export { type Doc$1 as Doc, ReactYjsPlugin, ReactYjsPluginProps };
@@ -0,0 +1,200 @@
1
+ "use client";
2
+ import { useLexicalComposerContext } from "../../../editor-kernel/react/react-context.js";
3
+ import { useLexicalEditor } from "../../../editor-kernel/react/useLexicalEditor.js";
4
+ import { ICodemirrorEditLockService } from "../../codemirror-block/service/index.js";
5
+ import { IYjsService } from "../service/index.js";
6
+ import { YjsPlugin } from "../plugin/index.js";
7
+ import { createCodemirrorEditLockProvider } from "./codemirrorEditLockProvider.js";
8
+ import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
9
+ import { jsx } from "react/jsx-runtime";
10
+ import { BLUR_COMMAND, COMMAND_PRIORITY_EDITOR, FOCUS_COMMAND } from "lexical";
11
+ import { createPortal } from "react-dom";
12
+ import { initLocalState, setLocalStateFocus, syncCursorPositions } from "@lexical/yjs";
13
+ //#region src/plugins/yjs/react/index.tsx
14
+ const DEFAULT_CURSOR_COLOR = "#2563eb";
15
+ const DEFAULT_USERNAME = "Anonymous";
16
+ const CURSOR_OVERLAY_Z_INDEX = 1;
17
+ function useYjsState(lexicalEditor, pluginRegisteredSignal) {
18
+ const [editor] = useLexicalComposerContext();
19
+ const [state, setState] = useState(null);
20
+ useEffect(() => {
21
+ if (!lexicalEditor) return;
22
+ return editor.requireService(IYjsService)?.subscribe(setState);
23
+ }, [
24
+ editor,
25
+ lexicalEditor,
26
+ pluginRegisteredSignal
27
+ ]);
28
+ return state;
29
+ }
30
+ function YjsCursors({ cursorsContainerRef, lexicalEditor, syncCursorPositionsFn, state }) {
31
+ const [defaultContainer, setDefaultContainer] = useState(null);
32
+ const cursorsContainer = cursorsContainerRef?.current || defaultContainer || document.body;
33
+ useLayoutEffect(() => {
34
+ if (cursorsContainerRef?.current) return;
35
+ const rootElement = lexicalEditor.getRootElement();
36
+ setDefaultContainer(rootElement?.parentElement || rootElement || document.body);
37
+ }, [cursorsContainerRef, lexicalEditor]);
38
+ return useMemo(() => {
39
+ const ref = (element) => {
40
+ state.binding.cursorsContainer = element;
41
+ if (element) syncCursorPositionsFn(state.binding, state.provider);
42
+ };
43
+ return createPortal(/* @__PURE__ */ jsx("div", {
44
+ ref,
45
+ style: {
46
+ height: 0,
47
+ left: 0,
48
+ pointerEvents: "none",
49
+ position: "absolute",
50
+ top: 0,
51
+ width: 0,
52
+ zIndex: CURSOR_OVERLAY_Z_INDEX
53
+ }
54
+ }), cursorsContainer);
55
+ }, [
56
+ cursorsContainer,
57
+ state.binding,
58
+ state.provider,
59
+ syncCursorPositionsFn
60
+ ]);
61
+ }
62
+ const ReactYjsPlugin = ({ awarenessData = {}, cursorColor = DEFAULT_CURSOR_COLOR, cursorsContainerRef, excludedProperties, id, initialEditorState, persistCursorOnBlur = true, providerFactory, shouldBootstrap, syncCursorPositionsFn = syncCursorPositions, username = DEFAULT_USERNAME, yjsDoc }) => {
63
+ const [editor] = useLexicalComposerContext();
64
+ const [lexicalEditor, setLexicalEditor] = useState(null);
65
+ const [pluginRegisteredSignal, setPluginRegisteredSignal] = useState(0);
66
+ const lastLocalSelectionRef = useRef(null);
67
+ const state = useYjsState(lexicalEditor, pluginRegisteredSignal);
68
+ useLayoutEffect(() => {
69
+ editor.registerPlugin(YjsPlugin, {
70
+ excludedProperties,
71
+ id,
72
+ initialEditorState,
73
+ providerFactory,
74
+ shouldBootstrap,
75
+ yjsDoc
76
+ });
77
+ setPluginRegisteredSignal((value) => value + 1);
78
+ }, [
79
+ editor,
80
+ excludedProperties,
81
+ id,
82
+ initialEditorState,
83
+ providerFactory,
84
+ shouldBootstrap,
85
+ yjsDoc
86
+ ]);
87
+ useLexicalEditor((activeEditor) => {
88
+ setLexicalEditor(activeEditor);
89
+ return () => {
90
+ setLexicalEditor(null);
91
+ };
92
+ }, []);
93
+ useEffect(() => {
94
+ if (!lexicalEditor || !state) return;
95
+ const awareness = state.provider.awareness;
96
+ const setLocalState = awareness.setLocalState.bind(awareness);
97
+ const getLocalStateWithPreservedSelection = (localState) => {
98
+ if (!localState) {
99
+ lastLocalSelectionRef.current = null;
100
+ return localState;
101
+ }
102
+ if (localState.anchorPos && localState.focusPos) {
103
+ lastLocalSelectionRef.current = {
104
+ anchorPos: localState.anchorPos,
105
+ focusPos: localState.focusPos
106
+ };
107
+ return localState;
108
+ }
109
+ if (!persistCursorOnBlur || !localState.focusing || !lastLocalSelectionRef.current) return localState;
110
+ return {
111
+ ...localState,
112
+ anchorPos: lastLocalSelectionRef.current.anchorPos,
113
+ focusPos: lastLocalSelectionRef.current.focusPos
114
+ };
115
+ };
116
+ awareness.setLocalState = (localState) => {
117
+ setLocalState(getLocalStateWithPreservedSelection(localState));
118
+ };
119
+ initLocalState(state.provider, username, cursorColor, persistCursorOnBlur || document.activeElement === lexicalEditor.getRootElement(), awarenessData);
120
+ const updateAwareness = () => {
121
+ const localState = awareness.getLocalState();
122
+ const nextLocalState = getLocalStateWithPreservedSelection(localState);
123
+ if (nextLocalState !== localState) setLocalState(nextLocalState);
124
+ syncCursorPositionsFn(state.binding, state.provider);
125
+ };
126
+ awareness.on("update", updateAwareness);
127
+ const unregisterUpdate = lexicalEditor.registerUpdateListener(updateAwareness);
128
+ let animationFrame = 0;
129
+ const renderCursorPositions = () => {
130
+ updateAwareness();
131
+ animationFrame = window.requestAnimationFrame(renderCursorPositions);
132
+ };
133
+ animationFrame = window.requestAnimationFrame(renderCursorPositions);
134
+ return () => {
135
+ awareness.setLocalState = setLocalState;
136
+ awareness.off("update", updateAwareness);
137
+ unregisterUpdate();
138
+ window.cancelAnimationFrame(animationFrame);
139
+ };
140
+ }, [
141
+ awarenessData,
142
+ cursorColor,
143
+ lexicalEditor,
144
+ persistCursorOnBlur,
145
+ state,
146
+ syncCursorPositionsFn,
147
+ username
148
+ ]);
149
+ useEffect(() => {
150
+ if (!lexicalEditor || !state) return;
151
+ return editor.requireService(ICodemirrorEditLockService)?.registerProvider(createCodemirrorEditLockProvider(state));
152
+ }, [
153
+ editor,
154
+ lexicalEditor,
155
+ state
156
+ ]);
157
+ useEffect(() => {
158
+ if (!lexicalEditor || !state) return;
159
+ const setFocus = (focusing) => {
160
+ if (!focusing && persistCursorOnBlur) return false;
161
+ setLocalStateFocus(state.provider, username, cursorColor, focusing, awarenessData);
162
+ return false;
163
+ };
164
+ const unregisterFocus = lexicalEditor.registerCommand(FOCUS_COMMAND, () => setFocus(true), COMMAND_PRIORITY_EDITOR);
165
+ const unregisterBlur = lexicalEditor.registerCommand(BLUR_COMMAND, () => setFocus(false), COMMAND_PRIORITY_EDITOR);
166
+ return () => {
167
+ unregisterFocus();
168
+ unregisterBlur();
169
+ };
170
+ }, [
171
+ awarenessData,
172
+ cursorColor,
173
+ lexicalEditor,
174
+ persistCursorOnBlur,
175
+ state,
176
+ username
177
+ ]);
178
+ useEffect(() => {
179
+ if (!state) return;
180
+ const clearAwarenessState = () => {
181
+ state.provider.awareness.setLocalState(null);
182
+ };
183
+ window.addEventListener("beforeunload", clearAwarenessState);
184
+ window.addEventListener("pagehide", clearAwarenessState);
185
+ return () => {
186
+ window.removeEventListener("beforeunload", clearAwarenessState);
187
+ window.removeEventListener("pagehide", clearAwarenessState);
188
+ clearAwarenessState();
189
+ };
190
+ }, [state]);
191
+ if (!lexicalEditor || !state) return null;
192
+ return /* @__PURE__ */ jsx(YjsCursors, {
193
+ cursorsContainerRef,
194
+ lexicalEditor,
195
+ state,
196
+ syncCursorPositionsFn
197
+ });
198
+ };
199
+ //#endregion
200
+ export { ReactYjsPlugin };