@lobehub/editor 4.24.0 → 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.
- package/es/codemirror/style.js +42 -0
- package/es/editor-kernel/kernel.js +3 -0
- package/es/editor-kernel/react/useDecorators.js +2 -4
- package/es/headless/collapsible-plugin.js +74 -0
- package/es/headless/index.js +2 -0
- package/es/headless.d.ts +18 -0
- package/es/headless.js +2479 -71
- package/es/index.d.ts +26 -3
- package/es/index.js +17 -2
- package/es/locale/index.d.ts +14 -0
- package/es/locale/index.js +14 -0
- package/es/plugins/block/command/index.js +24 -0
- package/es/plugins/block/plugin/index.js +7 -5
- package/es/plugins/block/react/ReactBlockPlugin.js +43 -1
- package/es/plugins/block/react/core/menu-position.js +20 -0
- package/es/plugins/block/react/drag/drag-session.js +3 -2
- package/es/plugins/block/react/drag/drag-utils.js +13 -1
- package/es/plugins/codemirror-block/index.d.ts +2 -1
- package/es/plugins/codemirror-block/plugin/index.js +2 -0
- package/es/plugins/codemirror-block/react/CodemirrorNode.js +95 -5
- package/es/plugins/codemirror-block/react/useCodemirrorEditLock.js +33 -0
- package/es/plugins/codemirror-block/service/index.d.ts +40 -0
- package/es/plugins/codemirror-block/service/index.js +59 -0
- package/es/plugins/collapsible/command/index.d.ts +17 -0
- package/es/plugins/collapsible/command/index.js +72 -0
- package/es/plugins/collapsible/index.d.ts +7 -0
- package/es/plugins/collapsible/node/CollapsibleNode.d.ts +30 -0
- package/es/plugins/collapsible/node/CollapsibleNode.js +138 -0
- package/es/plugins/collapsible/plugin/index.d.ts +11 -0
- package/es/plugins/collapsible/plugin/index.js +535 -0
- package/es/plugins/collapsible/react/ReactCollapsiblePlugin.d.ts +6 -0
- package/es/plugins/collapsible/react/ReactCollapsiblePlugin.js +19 -0
- package/es/plugins/collapsible/react/index.d.ts +3 -0
- package/es/plugins/collapsible/react/style.js +67 -0
- package/es/plugins/collapsible/react/type.d.ts +6 -0
- package/es/plugins/common/plugin/index.js +3 -2
- package/es/plugins/common/plugin/progressive-select-all.js +81 -0
- package/es/plugins/common/plugin/register.js +73 -4
- package/es/plugins/link/command/index.js +26 -3
- package/es/plugins/link/conversion/index.js +237 -0
- package/es/plugins/link/index.d.ts +5 -3
- package/es/plugins/link/node/LinkBlockCardNode.d.ts +16 -0
- package/es/plugins/link/node/LinkBlockCardNode.js +66 -0
- package/es/plugins/link/node/LinkCardNode.d.ts +42 -0
- package/es/plugins/link/node/LinkCardNode.js +161 -0
- package/es/plugins/link/node/LinkIframeNode.d.ts +33 -0
- package/es/plugins/link/node/LinkIframeNode.js +131 -0
- package/es/plugins/link/node/LinkNode.d.ts +48 -2
- package/es/plugins/link/node/LinkNode.js +14 -6
- package/es/plugins/link/node/SchemaNode.d.ts +36 -0
- package/es/plugins/link/node/SchemaNode.js +149 -0
- package/es/plugins/link/normalization/index.d.ts +7 -0
- package/es/plugins/link/normalization/index.js +38 -0
- package/es/plugins/link/plugin/index.d.ts +26 -1
- package/es/plugins/link/plugin/index.js +124 -4
- package/es/plugins/link/react/ReactLinkPlugin.js +165 -7
- package/es/plugins/link/react/components/LinkCard.js +235 -0
- package/es/plugins/link/react/components/LinkEdit.js +93 -14
- package/es/plugins/link/react/components/LinkIframe.js +128 -0
- package/es/plugins/link/react/components/LinkToolbar.js +440 -111
- package/es/plugins/link/react/components/SchemaLink.js +49 -0
- package/es/plugins/link/react/index.d.ts +2 -2
- package/es/plugins/link/react/renderer-registry.d.ts +81 -0
- package/es/plugins/link/react/renderer-registry.js +80 -0
- package/es/plugins/link/react/style.js +63 -1
- package/es/plugins/link/react/type.d.ts +24 -1
- package/es/plugins/link/service/i-link-service.d.ts +156 -1
- package/es/plugins/link/service/i-link-service.js +258 -5
- package/es/plugins/list/plugin/index.js +9 -2
- package/es/plugins/litexml/command/diffCommand.js +94 -14
- package/es/plugins/litexml/command/index.js +187 -6
- package/es/plugins/litexml/diff-validation.js +237 -0
- package/es/plugins/litexml/node/DiffContentNode.js +67 -0
- package/es/plugins/litexml/node/DiffNode.d.ts +1 -1
- package/es/plugins/litexml/node/DiffNode.js +5 -2
- package/es/plugins/litexml/node/TableCellDiffNode.js +77 -0
- package/es/plugins/litexml/node/TableRowDiffNode.js +73 -0
- package/es/plugins/litexml/plugin/index.d.ts +2 -0
- package/es/plugins/litexml/plugin/index.js +62 -9
- package/es/plugins/litexml/react/DiffNodeToolbar/index.js +10 -12
- package/es/plugins/litexml/react/TableRowDiffToolbar.js +123 -0
- package/es/plugins/litexml/react/hooks/useHasDiffNode.js +5 -2
- package/es/plugins/litexml/react/index.js +6 -2
- package/es/plugins/litexml/react/style.js +18 -5
- package/es/plugins/litexml/table-cell-diff.js +153 -0
- package/es/plugins/litexml/table-row-diff.js +82 -0
- package/es/plugins/markdown/data-source/markdown/parse.js +25 -1
- package/es/plugins/markdown/plugin/index.js +5 -3
- package/es/plugins/slash/react/ReactSlashPlugin.js +22 -43
- package/es/plugins/slash/react/components/DefaultSlashMenu.js +76 -16
- package/es/plugins/slash/react/menu-utils.js +84 -0
- package/es/plugins/slash/service/i-slash-service.d.ts +3 -0
- package/es/plugins/table/plugin/index.js +4 -1
- package/es/plugins/table/plugin/single-cell-paste.js +79 -0
- package/es/plugins/table/react/TableController/hooks.js +7 -4
- package/es/plugins/table/react/hooks.js +2 -2
- package/es/plugins/table/react/index.js +2 -0
- package/es/plugins/table/react/style.js +15 -5
- package/es/plugins/table/react/useScrollFocusedTableCellIntoView.js +46 -0
- package/es/plugins/toc/index.d.ts +10 -0
- package/es/plugins/toc/plugin/index.d.ts +8 -0
- package/es/plugins/toc/plugin/index.js +56 -0
- package/es/plugins/toc/react/ReactTocPlugin.d.ts +6 -0
- package/es/plugins/toc/react/ReactTocPlugin.js +36 -0
- package/es/plugins/toc/react/getNearestScrollContainer.d.ts +6 -0
- package/es/plugins/toc/react/getNearestScrollContainer.js +18 -0
- package/es/plugins/toc/react/hooks/useActiveHeading.js +59 -0
- package/es/plugins/toc/react/hooks/useTocItems.js +50 -0
- package/es/plugins/toc/react/index.d.ts +5 -0
- package/es/plugins/toc/react/type.d.ts +30 -0
- package/es/plugins/toc/react/useToc.d.ts +5 -0
- package/es/plugins/toc/react/useToc.js +41 -0
- package/es/plugins/toc/service/i-toc-service.d.ts +39 -0
- package/es/plugins/toc/service/i-toc-service.js +125 -0
- package/es/plugins/toc/service/index.d.ts +3 -0
- package/es/plugins/toc/types.d.ts +23 -0
- package/es/plugins/toc/utils/index.d.ts +6 -0
- package/es/plugins/toc/utils/index.js +30 -0
- package/es/plugins/toolbar/react/index.js +26 -12
- package/es/plugins/toolbar/react/style.js +3 -2
- package/es/plugins/virtual-block/plugin/register.js +34 -12
- package/es/plugins/yjs/index.d.ts +6 -0
- package/es/plugins/yjs/plugin/index.d.ts +7 -0
- package/es/plugins/yjs/plugin/index.js +191 -0
- package/es/plugins/yjs/plugin/types.d.ts +16 -0
- package/es/plugins/yjs/plugin/utils/awareness.js +9 -0
- package/es/plugins/yjs/plugin/utils/editor-state.js +47 -0
- package/es/plugins/yjs/plugin/utils/history.js +31 -0
- package/es/plugins/yjs/plugin/utils/node-properties.js +39 -0
- package/es/plugins/yjs/plugin/utils/sync.js +24 -0
- package/es/plugins/yjs/react/codemirrorEditLockProvider.js +59 -0
- package/es/plugins/yjs/react/index.d.ts +23 -0
- package/es/plugins/yjs/react/index.js +200 -0
- package/es/plugins/yjs/service/index.d.ts +47 -0
- package/es/plugins/yjs/service/index.js +84 -0
- package/es/plugins/yjs/websocket-provider.d.ts +78 -0
- package/es/plugins/yjs/websocket-provider.js +263 -0
- package/es/renderer/LexicalRenderer.js +9 -2
- package/es/renderer/engine/render-builtin-node.js +17 -8
- package/es/types/kernel.d.ts +4 -0
- package/es/utils/updatePosition.js +5 -2
- package/package.json +3 -1
- package/patches/@lexical__yjs@0.42.0.patch +42 -0
- package/scripts/postinstall-lexical-patch.cjs +62 -36
|
@@ -7,6 +7,7 @@ import { useTranslation } from "../../../editor-kernel/react/useTranslation.js";
|
|
|
7
7
|
import { SELECT_AFTER_CODEMIRROR_COMMAND, SELECT_BEFORE_CODEMIRROR_COMMAND } from "../command/index.js";
|
|
8
8
|
import { useLexicalNodeSelection } from "../../../editor-kernel/react/useLexicalNodeSelection.js";
|
|
9
9
|
import MermaidPreview from "./MermaidPreview.js";
|
|
10
|
+
import { useCodemirrorEditLock } from "./useCodemirrorEditLock.js";
|
|
10
11
|
import { ActionIcon, Block } from "@lobehub/ui";
|
|
11
12
|
import { CodeXml, Eye } from "lucide-react";
|
|
12
13
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
@@ -21,6 +22,8 @@ const ReactCodemirrorNode = ({ node, className, editor }) => {
|
|
|
21
22
|
const keydownRef = useRef("");
|
|
22
23
|
const instanceRef = useRef(null);
|
|
23
24
|
const isEmptyRef = useRef(false);
|
|
25
|
+
const hasLocalEditLockRef = useRef(false);
|
|
26
|
+
const releaseLockTimerRef = useRef(null);
|
|
24
27
|
const t = useTranslation();
|
|
25
28
|
const [isSelected, setSelected, clearSelection, isNodeSelected] = useLexicalNodeSelection(node.getKey());
|
|
26
29
|
const [selectedLang, setSelectedLang] = useState(node.lang || "javascript");
|
|
@@ -30,6 +33,48 @@ const ReactCodemirrorNode = ({ node, className, editor }) => {
|
|
|
30
33
|
const [expand, setExpand] = useState(true);
|
|
31
34
|
const [preview, setPreview] = useState(false);
|
|
32
35
|
const [code, setCode] = useState(node.code);
|
|
36
|
+
const { acquireLock, isLockedByRemote, lockOwnerName, releaseLock } = useCodemirrorEditLock(node.getKey(), "CodeMirror block");
|
|
37
|
+
const editLockRef = useRef({
|
|
38
|
+
acquireLock,
|
|
39
|
+
isLockedByRemote,
|
|
40
|
+
releaseLock
|
|
41
|
+
});
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
editLockRef.current = {
|
|
44
|
+
acquireLock,
|
|
45
|
+
isLockedByRemote,
|
|
46
|
+
releaseLock
|
|
47
|
+
};
|
|
48
|
+
}, [
|
|
49
|
+
acquireLock,
|
|
50
|
+
isLockedByRemote,
|
|
51
|
+
releaseLock
|
|
52
|
+
]);
|
|
53
|
+
const clearReleaseLockTimer = useCallback(() => {
|
|
54
|
+
if (!releaseLockTimerRef.current) return;
|
|
55
|
+
clearTimeout(releaseLockTimerRef.current);
|
|
56
|
+
releaseLockTimerRef.current = null;
|
|
57
|
+
}, []);
|
|
58
|
+
const acquireEditLock = useCallback(() => {
|
|
59
|
+
clearReleaseLockTimer();
|
|
60
|
+
const acquired = editLockRef.current.acquireLock();
|
|
61
|
+
if (acquired) hasLocalEditLockRef.current = true;
|
|
62
|
+
return acquired;
|
|
63
|
+
}, [clearReleaseLockTimer]);
|
|
64
|
+
const releaseEditLock = useCallback(() => {
|
|
65
|
+
clearReleaseLockTimer();
|
|
66
|
+
if (!hasLocalEditLockRef.current) return;
|
|
67
|
+
editLockRef.current.releaseLock();
|
|
68
|
+
hasLocalEditLockRef.current = false;
|
|
69
|
+
}, [clearReleaseLockTimer]);
|
|
70
|
+
const scheduleReleaseEditLock = useCallback(() => {
|
|
71
|
+
clearReleaseLockTimer();
|
|
72
|
+
releaseLockTimerRef.current = setTimeout(() => {
|
|
73
|
+
releaseLockTimerRef.current = null;
|
|
74
|
+
if (document.visibilityState === "hidden" || instanceRef.current?.view.hasFocus) return;
|
|
75
|
+
releaseEditLock();
|
|
76
|
+
}, 100);
|
|
77
|
+
}, [clearReleaseLockTimer, releaseEditLock]);
|
|
33
78
|
const handleCopy = useCallback(async () => {
|
|
34
79
|
if (instanceRef.current) {
|
|
35
80
|
const code = instanceRef.current.getValue();
|
|
@@ -83,14 +128,20 @@ const ReactCodemirrorNode = ({ node, className, editor }) => {
|
|
|
83
128
|
return;
|
|
84
129
|
}
|
|
85
130
|
if (isSelected && instanceRef.current && isNodeSelected) {
|
|
131
|
+
if (!acquireEditLock()) {
|
|
132
|
+
clearSelection();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
86
135
|
if (instanceRef.current?.view.hasFocus) return;
|
|
87
136
|
instanceRef.current.focus();
|
|
88
137
|
if (keydownRef.current === "end") instanceRef.current.setSelectionToEnd();
|
|
89
138
|
}
|
|
90
139
|
}, [
|
|
91
|
-
|
|
140
|
+
acquireEditLock,
|
|
141
|
+
clearSelection,
|
|
142
|
+
editor,
|
|
92
143
|
isNodeSelected,
|
|
93
|
-
|
|
144
|
+
isSelected
|
|
94
145
|
]);
|
|
95
146
|
useEffect(() => {
|
|
96
147
|
if (instanceRef.current) return;
|
|
@@ -102,6 +153,7 @@ const ReactCodemirrorNode = ({ node, className, editor }) => {
|
|
|
102
153
|
indentWithTabs: useTabs,
|
|
103
154
|
lineNumbers: showLineNumbers,
|
|
104
155
|
mode: node.lang,
|
|
156
|
+
readOnly: editLockRef.current.isLockedByRemote,
|
|
105
157
|
tabSize,
|
|
106
158
|
theme: "default",
|
|
107
159
|
value: node.code
|
|
@@ -162,6 +214,11 @@ const ReactCodemirrorNode = ({ node, className, editor }) => {
|
|
|
162
214
|
});
|
|
163
215
|
}));
|
|
164
216
|
instance.on("focus", () => {
|
|
217
|
+
if (!acquireEditLock()) {
|
|
218
|
+
instanceRef.current?.blur();
|
|
219
|
+
clearSelection();
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
165
222
|
if (editor.getEditorState().read(() => {
|
|
166
223
|
const sel = $getSelection();
|
|
167
224
|
if (!sel) return false;
|
|
@@ -169,11 +226,13 @@ const ReactCodemirrorNode = ({ node, className, editor }) => {
|
|
|
169
226
|
return false;
|
|
170
227
|
})) setSelected(true);
|
|
171
228
|
});
|
|
229
|
+
instance.on("blur", scheduleReleaseEditLock);
|
|
172
230
|
instanceRef.current = instance;
|
|
173
|
-
if (isSelected) instanceRef.current.focus();
|
|
231
|
+
if (isSelected && acquireEditLock()) instanceRef.current.focus();
|
|
174
232
|
});
|
|
175
233
|
}
|
|
176
234
|
return () => {
|
|
235
|
+
releaseEditLock();
|
|
177
236
|
if (instanceRef.current) {
|
|
178
237
|
instanceRef.current.destroy();
|
|
179
238
|
instanceRef.current = null;
|
|
@@ -193,13 +252,44 @@ const ReactCodemirrorNode = ({ node, className, editor }) => {
|
|
|
193
252
|
node,
|
|
194
253
|
setSelected
|
|
195
254
|
]);
|
|
255
|
+
useEffect(() => {
|
|
256
|
+
const handleVisibilityChange = () => {
|
|
257
|
+
if (document.visibilityState === "visible" && !instanceRef.current?.view.hasFocus) releaseEditLock();
|
|
258
|
+
};
|
|
259
|
+
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
260
|
+
return () => document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
261
|
+
}, [releaseEditLock]);
|
|
262
|
+
useEffect(() => {
|
|
263
|
+
instanceRef.current?.setOption("readOnly", isLockedByRemote);
|
|
264
|
+
if (isLockedByRemote) {
|
|
265
|
+
releaseEditLock();
|
|
266
|
+
instanceRef.current?.blur();
|
|
267
|
+
clearSelection();
|
|
268
|
+
}
|
|
269
|
+
}, [
|
|
270
|
+
clearSelection,
|
|
271
|
+
isLockedByRemote,
|
|
272
|
+
releaseEditLock
|
|
273
|
+
]);
|
|
274
|
+
const handleBlockMouseDown = useCallback((event) => {
|
|
275
|
+
event.stopPropagation();
|
|
276
|
+
if (!isLockedByRemote) return;
|
|
277
|
+
event.preventDefault();
|
|
278
|
+
instanceRef.current?.blur();
|
|
279
|
+
clearSelection();
|
|
280
|
+
}, [clearSelection, isLockedByRemote]);
|
|
196
281
|
return /* @__PURE__ */ jsxs(Block, {
|
|
197
|
-
className: cx(styles, isSelected && !isNodeSelected && "selected", className),
|
|
198
|
-
onMouseDown:
|
|
282
|
+
className: cx(styles, isSelected && !isNodeSelected && "selected", isLockedByRemote && "collab-locked", className),
|
|
283
|
+
onMouseDown: handleBlockMouseDown,
|
|
199
284
|
onMouseUp: (e) => e.stopPropagation(),
|
|
200
285
|
onSelect: (e) => e.stopPropagation(),
|
|
201
286
|
variant: "filled",
|
|
202
287
|
children: [
|
|
288
|
+
isLockedByRemote && /* @__PURE__ */ jsxs("div", {
|
|
289
|
+
className: "cm-collab-lock",
|
|
290
|
+
contentEditable: false,
|
|
291
|
+
children: [lockOwnerName || "Someone", " editing"]
|
|
292
|
+
}),
|
|
203
293
|
/* @__PURE__ */ jsx(Toolbar, {
|
|
204
294
|
expand,
|
|
205
295
|
extra: selectedLang === "mermaid" ? /* @__PURE__ */ jsx(ActionIcon, {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useLexicalComposerContext } from "../../../editor-kernel/react/react-context.js";
|
|
2
|
+
import { ICodemirrorEditLockService } from "../service/index.js";
|
|
3
|
+
import { useCallback, useEffect, useState } from "react";
|
|
4
|
+
//#region src/plugins/codemirror-block/react/useCodemirrorEditLock.ts
|
|
5
|
+
function useCodemirrorEditLock(key, label) {
|
|
6
|
+
const [editor] = useLexicalComposerContext();
|
|
7
|
+
const service = editor.requireService(ICodemirrorEditLockService);
|
|
8
|
+
const [, setVersion] = useState(0);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
return service?.subscribe(() => {
|
|
11
|
+
setVersion((value) => value + 1);
|
|
12
|
+
});
|
|
13
|
+
}, [service]);
|
|
14
|
+
const remoteLockOwner = service?.getRemoteLockOwner(key) || null;
|
|
15
|
+
const acquireLock = useCallback(() => {
|
|
16
|
+
return service?.acquireLock(key, label) ?? true;
|
|
17
|
+
}, [
|
|
18
|
+
key,
|
|
19
|
+
label,
|
|
20
|
+
service
|
|
21
|
+
]);
|
|
22
|
+
const releaseLock = useCallback(() => {
|
|
23
|
+
service?.releaseLock(key);
|
|
24
|
+
}, [key, service]);
|
|
25
|
+
return {
|
|
26
|
+
acquireLock,
|
|
27
|
+
isLockedByRemote: !!remoteLockOwner,
|
|
28
|
+
lockOwnerName: remoteLockOwner?.name,
|
|
29
|
+
releaseLock
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { useCodemirrorEditLock };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { IServiceID } from "../../../types/kernel.js";
|
|
2
|
+
import "../../../types/index.js";
|
|
3
|
+
//#region src/plugins/codemirror-block/service/index.d.ts
|
|
4
|
+
interface CodemirrorEditLock {
|
|
5
|
+
key: string;
|
|
6
|
+
label?: string;
|
|
7
|
+
updatedAt: number;
|
|
8
|
+
}
|
|
9
|
+
interface CodemirrorEditLockOwner {
|
|
10
|
+
id: number | string;
|
|
11
|
+
lock: CodemirrorEditLock;
|
|
12
|
+
name?: string;
|
|
13
|
+
}
|
|
14
|
+
interface CodemirrorEditLockProvider {
|
|
15
|
+
acquireLock: (key: string, label: string) => boolean;
|
|
16
|
+
getRemoteLockOwner: (key: string) => CodemirrorEditLockOwner | null;
|
|
17
|
+
releaseLock: (key: string) => void;
|
|
18
|
+
subscribe: (listener: () => void) => () => void;
|
|
19
|
+
}
|
|
20
|
+
interface ICodemirrorEditLockService {
|
|
21
|
+
acquireLock: (key: string, label: string) => boolean;
|
|
22
|
+
getRemoteLockOwner: (key: string) => CodemirrorEditLockOwner | null;
|
|
23
|
+
registerProvider: (provider: CodemirrorEditLockProvider) => () => void;
|
|
24
|
+
releaseLock: (key: string) => void;
|
|
25
|
+
subscribe: (listener: () => void) => () => void;
|
|
26
|
+
}
|
|
27
|
+
declare const ICodemirrorEditLockService: IServiceID<ICodemirrorEditLockService>;
|
|
28
|
+
declare class CodemirrorEditLockService implements ICodemirrorEditLockService {
|
|
29
|
+
private providerUnsubscribers;
|
|
30
|
+
private providers;
|
|
31
|
+
private subscribers;
|
|
32
|
+
acquireLock(key: string, label: string): boolean;
|
|
33
|
+
getRemoteLockOwner(key: string): CodemirrorEditLockOwner | null;
|
|
34
|
+
registerProvider(provider: CodemirrorEditLockProvider): () => void;
|
|
35
|
+
releaseLock(key: string): void;
|
|
36
|
+
subscribe(listener: () => void): () => void;
|
|
37
|
+
private notify;
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
export { CodemirrorEditLock, CodemirrorEditLockOwner, CodemirrorEditLockProvider, CodemirrorEditLockService, ICodemirrorEditLockService };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { genServiceId, init_utils } from "../../../editor-kernel/utils.js";
|
|
2
|
+
//#region src/plugins/codemirror-block/service/index.ts
|
|
3
|
+
init_utils();
|
|
4
|
+
const ICodemirrorEditLockService = genServiceId("CodemirrorEditLockService");
|
|
5
|
+
var CodemirrorEditLockService = class {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.providerUnsubscribers = /* @__PURE__ */ new Map();
|
|
8
|
+
this.providers = /* @__PURE__ */ new Set();
|
|
9
|
+
this.subscribers = /* @__PURE__ */ new Set();
|
|
10
|
+
}
|
|
11
|
+
acquireLock(key, label) {
|
|
12
|
+
if (this.getRemoteLockOwner(key)) return false;
|
|
13
|
+
const acquiredProviders = [];
|
|
14
|
+
for (const provider of this.providers) {
|
|
15
|
+
if (!provider.acquireLock(key, label)) {
|
|
16
|
+
acquiredProviders.forEach((acquiredProvider) => acquiredProvider.releaseLock(key));
|
|
17
|
+
this.notify();
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
acquiredProviders.push(provider);
|
|
21
|
+
}
|
|
22
|
+
this.notify();
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
getRemoteLockOwner(key) {
|
|
26
|
+
for (const provider of this.providers) {
|
|
27
|
+
const owner = provider.getRemoteLockOwner(key);
|
|
28
|
+
if (owner) return owner;
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
registerProvider(provider) {
|
|
33
|
+
this.providers.add(provider);
|
|
34
|
+
this.providerUnsubscribers.set(provider, provider.subscribe(() => this.notify()));
|
|
35
|
+
this.notify();
|
|
36
|
+
return () => {
|
|
37
|
+
this.providerUnsubscribers.get(provider)?.();
|
|
38
|
+
this.providerUnsubscribers.delete(provider);
|
|
39
|
+
this.providers.delete(provider);
|
|
40
|
+
this.notify();
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
releaseLock(key) {
|
|
44
|
+
this.providers.forEach((provider) => provider.releaseLock(key));
|
|
45
|
+
this.notify();
|
|
46
|
+
}
|
|
47
|
+
subscribe(listener) {
|
|
48
|
+
this.subscribers.add(listener);
|
|
49
|
+
listener();
|
|
50
|
+
return () => {
|
|
51
|
+
this.subscribers.delete(listener);
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
notify() {
|
|
55
|
+
this.subscribers.forEach((listener) => listener());
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
//#endregion
|
|
59
|
+
export { CodemirrorEditLockService, ICodemirrorEditLockService };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import "../node/CollapsibleNode.js";
|
|
2
|
+
import { LexicalEditor, LexicalNode, NodeKey } from "lexical";
|
|
3
|
+
//#region src/plugins/collapsible/command/index.d.ts
|
|
4
|
+
interface InsertCollapsiblePayload {
|
|
5
|
+
children?: LexicalNode[];
|
|
6
|
+
collapsed?: boolean;
|
|
7
|
+
title?: string;
|
|
8
|
+
}
|
|
9
|
+
interface UpdateCollapsiblePayload {
|
|
10
|
+
collapsed?: boolean;
|
|
11
|
+
nodeKey: NodeKey;
|
|
12
|
+
title?: string;
|
|
13
|
+
}
|
|
14
|
+
declare const INSERT_COLLAPSIBLE_COMMAND: import("lexical").LexicalCommand<InsertCollapsiblePayload | undefined>;
|
|
15
|
+
declare const UPDATE_COLLAPSIBLE_COMMAND: import("lexical").LexicalCommand<UpdateCollapsiblePayload>;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { INSERT_COLLAPSIBLE_COMMAND, InsertCollapsiblePayload, UPDATE_COLLAPSIBLE_COMMAND, UpdateCollapsiblePayload };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { $createCollapsibleNode, $isCollapsibleNode } from "../node/CollapsibleNode.js";
|
|
2
|
+
import { $createParagraphNode, $createTextNode, $getNodeByKey, $getSelection, $insertNodes, $isRangeSelection, COMMAND_PRIORITY_EDITOR, createCommand } from "lexical";
|
|
3
|
+
//#region src/plugins/collapsible/command/index.ts
|
|
4
|
+
const INSERT_COLLAPSIBLE_COMMAND = createCommand("INSERT_COLLAPSIBLE_COMMAND");
|
|
5
|
+
const UPDATE_COLLAPSIBLE_COMMAND = createCommand("UPDATE_COLLAPSIBLE_COMMAND");
|
|
6
|
+
function registerCollapsibleCommand(editor, canCollapse = () => true) {
|
|
7
|
+
const unregisterInsert = editor.registerCommand(INSERT_COLLAPSIBLE_COMMAND, (payload = {}) => {
|
|
8
|
+
editor.update(() => {
|
|
9
|
+
const payloadChildren = payload.children;
|
|
10
|
+
const hasPayloadChildren = Boolean(payloadChildren?.length);
|
|
11
|
+
const title = payload.title ?? (hasPayloadChildren ? "Details" : "");
|
|
12
|
+
const collapsibleNode = $createCollapsibleNode(title, Boolean(payload.collapsed));
|
|
13
|
+
let children;
|
|
14
|
+
if (payloadChildren?.length) children = ensureTitleChild(payloadChildren, title);
|
|
15
|
+
else {
|
|
16
|
+
const titleChild = $createParagraphNode();
|
|
17
|
+
if (title) titleChild.append($createTextNode(title));
|
|
18
|
+
children = [titleChild, $createParagraphNode()];
|
|
19
|
+
}
|
|
20
|
+
collapsibleNode.append(...children);
|
|
21
|
+
const parentCollapsible = $getSelectedCollapsibleAncestor();
|
|
22
|
+
if (parentCollapsible) parentCollapsible.insertAfter(collapsibleNode);
|
|
23
|
+
else $insertNodes([collapsibleNode]);
|
|
24
|
+
collapsibleNode.selectStart();
|
|
25
|
+
});
|
|
26
|
+
return true;
|
|
27
|
+
}, COMMAND_PRIORITY_EDITOR);
|
|
28
|
+
const unregisterUpdate = editor.registerCommand(UPDATE_COLLAPSIBLE_COMMAND, ({ collapsed, nodeKey, title }) => {
|
|
29
|
+
editor.update(() => {
|
|
30
|
+
const node = $getNodeByKey(nodeKey);
|
|
31
|
+
if (!$isCollapsibleNode(node)) return;
|
|
32
|
+
if (title !== void 0) {
|
|
33
|
+
node.setTitle(title);
|
|
34
|
+
updateTitleChild(node, title);
|
|
35
|
+
}
|
|
36
|
+
if (collapsed !== void 0 && (!collapsed || canCollapse(node))) node.setCollapsed(collapsed);
|
|
37
|
+
});
|
|
38
|
+
return true;
|
|
39
|
+
}, COMMAND_PRIORITY_EDITOR);
|
|
40
|
+
return () => {
|
|
41
|
+
unregisterInsert();
|
|
42
|
+
unregisterUpdate();
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function $getSelectedCollapsibleAncestor() {
|
|
46
|
+
const selection = $getSelection();
|
|
47
|
+
if (!$isRangeSelection(selection)) return null;
|
|
48
|
+
let current = selection.anchor.getNode();
|
|
49
|
+
while (current) {
|
|
50
|
+
if ($isCollapsibleNode(current)) return current;
|
|
51
|
+
current = current.getParent();
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
function $createTitleParagraph(title) {
|
|
56
|
+
const paragraph = $createParagraphNode();
|
|
57
|
+
paragraph.append($createTextNode(title));
|
|
58
|
+
return paragraph;
|
|
59
|
+
}
|
|
60
|
+
function ensureTitleChild(children, title) {
|
|
61
|
+
if (!title) return children;
|
|
62
|
+
if (children[0]?.getTextContent().trim() === title.trim()) return children;
|
|
63
|
+
return [$createTitleParagraph(title), ...children];
|
|
64
|
+
}
|
|
65
|
+
function updateTitleChild(node, title) {
|
|
66
|
+
const titleParagraph = $createTitleParagraph(title);
|
|
67
|
+
const firstChild = node.getFirstChild();
|
|
68
|
+
if (firstChild) firstChild.replace(titleParagraph);
|
|
69
|
+
else node.append(titleParagraph);
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
72
|
+
export { INSERT_COLLAPSIBLE_COMMAND, UPDATE_COLLAPSIBLE_COMMAND, registerCollapsibleCommand };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { $createCollapsibleNode, $isCollapsibleNode, CollapsibleNode, SerializedCollapsibleNode } from "./node/CollapsibleNode.js";
|
|
2
|
+
import { INSERT_COLLAPSIBLE_COMMAND, UPDATE_COLLAPSIBLE_COMMAND } from "./command/index.js";
|
|
3
|
+
import { CollapsiblePlugin, CollapsiblePluginOptions } from "./plugin/index.js";
|
|
4
|
+
import { ReactCollapsiblePluginProps } from "./react/type.js";
|
|
5
|
+
import ReactCollapsiblePlugin from "./react/ReactCollapsiblePlugin.js";
|
|
6
|
+
import "./react/index.js";
|
|
7
|
+
export { $createCollapsibleNode, $isCollapsibleNode, CollapsibleNode, CollapsiblePlugin, CollapsiblePluginOptions, INSERT_COLLAPSIBLE_COMMAND, ReactCollapsiblePlugin, type ReactCollapsiblePluginProps, SerializedCollapsibleNode, UPDATE_COLLAPSIBLE_COMMAND };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { DOMConversionMap, DOMExportOutput, EditorConfig, ElementDOMSlot, ElementNode, LexicalEditor, LexicalNode, LexicalUpdateJSON, NodeKey, SerializedElementNode, Spread } from "lexical";
|
|
2
|
+
//#region src/plugins/collapsible/node/CollapsibleNode.d.ts
|
|
3
|
+
type SerializedCollapsibleNode = Spread<{
|
|
4
|
+
collapsed: boolean;
|
|
5
|
+
title: string;
|
|
6
|
+
}, SerializedElementNode>;
|
|
7
|
+
declare class CollapsibleNode extends ElementNode {
|
|
8
|
+
__collapsed: boolean;
|
|
9
|
+
__title: string;
|
|
10
|
+
static getType(): string;
|
|
11
|
+
static clone(node: CollapsibleNode): CollapsibleNode;
|
|
12
|
+
static importDOM(): DOMConversionMap | null;
|
|
13
|
+
static importJSON(serializedNode: SerializedCollapsibleNode): CollapsibleNode;
|
|
14
|
+
constructor(title?: string, collapsed?: boolean, key?: NodeKey);
|
|
15
|
+
createDOM(config: EditorConfig, editor: LexicalEditor): HTMLElement;
|
|
16
|
+
updateDOM(prevNode: this, dom: HTMLElement): boolean;
|
|
17
|
+
exportDOM(): DOMExportOutput;
|
|
18
|
+
exportJSON(): SerializedCollapsibleNode;
|
|
19
|
+
getDOMSlot(element: HTMLElement): ElementDOMSlot<HTMLElement>;
|
|
20
|
+
getTitle(): string;
|
|
21
|
+
isCollapsed(): boolean;
|
|
22
|
+
isShadowRoot(): boolean;
|
|
23
|
+
setCollapsed(collapsed: boolean): this;
|
|
24
|
+
setTitle(title: string): this;
|
|
25
|
+
updateFromJSON(serializedNode: LexicalUpdateJSON<SerializedCollapsibleNode>): this;
|
|
26
|
+
}
|
|
27
|
+
declare function $createCollapsibleNode(title?: string, collapsed?: boolean): CollapsibleNode;
|
|
28
|
+
declare function $isCollapsibleNode(node: LexicalNode | null | undefined): node is CollapsibleNode;
|
|
29
|
+
//#endregion
|
|
30
|
+
export { $createCollapsibleNode, $isCollapsibleNode, CollapsibleNode, SerializedCollapsibleNode };
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { $applyNodeReplacement, $createParagraphNode, $createTextNode, ElementNode } from "lexical";
|
|
2
|
+
import { addClassNamesToElement } from "@lexical/utils";
|
|
3
|
+
//#region src/plugins/collapsible/node/CollapsibleNode.ts
|
|
4
|
+
const CONTENT_SELECTOR = "[data-collapsible-content=\"true\"]";
|
|
5
|
+
const TOGGLE_SELECTOR = "[data-collapsible-toggle=\"true\"]";
|
|
6
|
+
var CollapsibleNode = class CollapsibleNode extends ElementNode {
|
|
7
|
+
static getType() {
|
|
8
|
+
return "collapsible";
|
|
9
|
+
}
|
|
10
|
+
static clone(node) {
|
|
11
|
+
return new CollapsibleNode(node.__title, node.__collapsed, node.__key);
|
|
12
|
+
}
|
|
13
|
+
static importDOM() {
|
|
14
|
+
return { details: () => ({
|
|
15
|
+
conversion: $convertDetailsElement,
|
|
16
|
+
priority: 1
|
|
17
|
+
}) };
|
|
18
|
+
}
|
|
19
|
+
static importJSON(serializedNode) {
|
|
20
|
+
const node = $createCollapsibleNode(serializedNode.title || "Details").updateFromJSON(serializedNode);
|
|
21
|
+
if (shouldPrependTitle(serializedNode)) {
|
|
22
|
+
const titleParagraph = $createTitleParagraph(serializedNode.title || "Details");
|
|
23
|
+
const firstChild = node.getFirstChild();
|
|
24
|
+
if (firstChild) firstChild.insertBefore(titleParagraph);
|
|
25
|
+
else node.append(titleParagraph);
|
|
26
|
+
}
|
|
27
|
+
return node;
|
|
28
|
+
}
|
|
29
|
+
constructor(title = "Details", collapsed = false, key) {
|
|
30
|
+
super(key);
|
|
31
|
+
this.__title = title;
|
|
32
|
+
this.__collapsed = collapsed;
|
|
33
|
+
}
|
|
34
|
+
createDOM(config, editor) {
|
|
35
|
+
const element = document.createElement("section");
|
|
36
|
+
addClassNamesToElement(element, config.theme.collapsible);
|
|
37
|
+
element.dataset.collapsible = "true";
|
|
38
|
+
element.dataset.collapsibleCollapsed = String(this.__collapsed);
|
|
39
|
+
const toggle = document.createElement("button");
|
|
40
|
+
toggle.type = "button";
|
|
41
|
+
toggle.contentEditable = "false";
|
|
42
|
+
toggle.dataset.collapsibleToggle = "true";
|
|
43
|
+
toggle.setAttribute("aria-expanded", String(!this.__collapsed));
|
|
44
|
+
toggle.setAttribute("aria-label", this.__collapsed ? "Expand collapsible block" : "Collapse collapsible block");
|
|
45
|
+
toggle.addEventListener("click", () => {
|
|
46
|
+
editor.update(() => {
|
|
47
|
+
const latest = this.getLatest();
|
|
48
|
+
if ($isCollapsibleNode(latest)) latest.setCollapsed(!latest.isCollapsed());
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
const content = document.createElement("div");
|
|
52
|
+
content.dataset.collapsibleContent = "true";
|
|
53
|
+
element.append(toggle, content);
|
|
54
|
+
return element;
|
|
55
|
+
}
|
|
56
|
+
updateDOM(prevNode, dom) {
|
|
57
|
+
const toggle = dom.querySelector(TOGGLE_SELECTOR);
|
|
58
|
+
dom.dataset.collapsibleCollapsed = String(this.__collapsed);
|
|
59
|
+
if (toggle && prevNode.__collapsed !== this.__collapsed) {
|
|
60
|
+
toggle.setAttribute("aria-expanded", String(!this.__collapsed));
|
|
61
|
+
toggle.setAttribute("aria-label", this.__collapsed ? "Expand collapsible block" : "Collapse collapsible block");
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
exportDOM() {
|
|
66
|
+
const element = document.createElement("details");
|
|
67
|
+
if (!this.__collapsed) element.setAttribute("open", "");
|
|
68
|
+
const summary = document.createElement("summary");
|
|
69
|
+
summary.textContent = this.getTitle();
|
|
70
|
+
element.append(summary);
|
|
71
|
+
return { element };
|
|
72
|
+
}
|
|
73
|
+
exportJSON() {
|
|
74
|
+
return {
|
|
75
|
+
...super.exportJSON(),
|
|
76
|
+
collapsed: this.isCollapsed(),
|
|
77
|
+
title: this.getTitle()
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
getDOMSlot(element) {
|
|
81
|
+
const content = element.querySelector(CONTENT_SELECTOR);
|
|
82
|
+
if (content instanceof HTMLElement) return super.getDOMSlot(element).withElement(content);
|
|
83
|
+
return super.getDOMSlot(element);
|
|
84
|
+
}
|
|
85
|
+
getTitle() {
|
|
86
|
+
const latest = this.getLatest();
|
|
87
|
+
const firstChild = latest.getFirstChild();
|
|
88
|
+
return firstChild ? firstChild.getTextContent().trim() : latest.__title;
|
|
89
|
+
}
|
|
90
|
+
isCollapsed() {
|
|
91
|
+
return this.getLatest().__collapsed;
|
|
92
|
+
}
|
|
93
|
+
isShadowRoot() {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
setCollapsed(collapsed) {
|
|
97
|
+
const writable = this.getWritable();
|
|
98
|
+
writable.__collapsed = collapsed;
|
|
99
|
+
return writable;
|
|
100
|
+
}
|
|
101
|
+
setTitle(title) {
|
|
102
|
+
const writable = this.getWritable();
|
|
103
|
+
writable.__title = title;
|
|
104
|
+
return writable;
|
|
105
|
+
}
|
|
106
|
+
updateFromJSON(serializedNode) {
|
|
107
|
+
return super.updateFromJSON(serializedNode).setTitle(serializedNode.title || "Details").setCollapsed(Boolean(serializedNode.collapsed));
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
function $createCollapsibleNode(title = "Details", collapsed = false) {
|
|
111
|
+
return $applyNodeReplacement(new CollapsibleNode(title, collapsed));
|
|
112
|
+
}
|
|
113
|
+
function $isCollapsibleNode(node) {
|
|
114
|
+
return node instanceof CollapsibleNode;
|
|
115
|
+
}
|
|
116
|
+
function $convertDetailsElement(domNode) {
|
|
117
|
+
if (!(domNode instanceof HTMLDetailsElement)) return { node: null };
|
|
118
|
+
return { node: $createCollapsibleNode(domNode.querySelector("summary")?.textContent?.trim() || "Details", !domNode.open) };
|
|
119
|
+
}
|
|
120
|
+
function $createTitleParagraph(title) {
|
|
121
|
+
const paragraph = $createParagraphNode();
|
|
122
|
+
paragraph.append($createTextNode(title));
|
|
123
|
+
return paragraph;
|
|
124
|
+
}
|
|
125
|
+
function getSerializedText(node) {
|
|
126
|
+
if (!node || typeof node !== "object") return "";
|
|
127
|
+
if ("text" in node && typeof node.text === "string") return node.text;
|
|
128
|
+
if ("children" in node && Array.isArray(node.children)) return node.children.map((child) => getSerializedText(child)).join("");
|
|
129
|
+
return "";
|
|
130
|
+
}
|
|
131
|
+
function shouldPrependTitle(serializedNode) {
|
|
132
|
+
const title = serializedNode.title?.trim();
|
|
133
|
+
if (!title) return false;
|
|
134
|
+
const firstChild = (Array.isArray(serializedNode.children) ? serializedNode.children : [])[0];
|
|
135
|
+
return getSerializedText(firstChild).trim() !== title;
|
|
136
|
+
}
|
|
137
|
+
//#endregion
|
|
138
|
+
export { $createCollapsibleNode, $isCollapsibleNode, CollapsibleNode };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { IEditorPluginConstructor } from "../../../types/kernel.js";
|
|
2
|
+
import "../../../types/index.js";
|
|
3
|
+
//#region src/plugins/collapsible/plugin/index.d.ts
|
|
4
|
+
interface CollapsiblePluginOptions {
|
|
5
|
+
theme?: {
|
|
6
|
+
collapsible?: string;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
declare const CollapsiblePlugin: IEditorPluginConstructor<CollapsiblePluginOptions>;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { CollapsiblePlugin, CollapsiblePluginOptions };
|