@haklex/rich-plugin-block-handle 0.1.1 → 0.3.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/dist/index.mjs +1248 -1379
- package/dist/rich-plugin-block-handle.css +2 -1
- package/package.json +22 -22
package/dist/index.mjs
CHANGED
|
@@ -1,1455 +1,1324 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuGroup, DropdownMenuLabel, DropdownMenuItem, DropdownMenuSeparator } from "@haklex/rich-editor-ui";
|
|
1
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "@haklex/rich-editor-ui";
|
|
3
2
|
import { usePortalTheme } from "@haklex/rich-style-token";
|
|
4
3
|
import { $createCodeNode } from "@lexical/code-core";
|
|
5
4
|
import { INSERT_CHECK_LIST_COMMAND, INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND } from "@lexical/list";
|
|
6
5
|
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
7
6
|
import { INSERT_HORIZONTAL_RULE_COMMAND } from "@lexical/react/LexicalHorizontalRuleNode";
|
|
8
|
-
import { $
|
|
7
|
+
import { $createHeadingNode, $createQuoteNode } from "@lexical/rich-text";
|
|
9
8
|
import { $setBlocksType } from "@lexical/selection";
|
|
10
|
-
import { $
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
9
|
+
import { $createNodeSelection, $createParagraphNode, $createTabNode, $getNearestNodeFromDOMNode, $getNodeByKey, $getRoot, $getSelection, $isElementNode, $isNodeSelection, $isRangeSelection, $parseSerializedNode, $setSelection, COMMAND_PRIORITY_CRITICAL, COMMAND_PRIORITY_HIGH, COPY_COMMAND, CUT_COMMAND, DELETE_CHARACTER_COMMAND, DRAGOVER_COMMAND, DRAGSTART_COMMAND, DROP_COMMAND, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_UP_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, KEY_DOWN_COMMAND, KEY_ESCAPE_COMMAND, PASTE_COMMAND, PASTE_TAG, REMOVE_TEXT_COMMAND, SELECT_ALL_COMMAND } from "lexical";
|
|
10
|
+
import { ArrowDown, ArrowUp, Code2, Copy, GripVertical, Heading1, Heading2, Heading3, List, ListChecks, ListOrdered, Minus, Plus, TextQuote, Trash2, Type } from "lucide-react";
|
|
11
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
13
12
|
import { createPortal } from "react-dom";
|
|
14
13
|
import { $generateNodesFromDOM } from "@lexical/html";
|
|
14
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
15
|
+
//#region src/styles.css.ts
|
|
15
16
|
var handleContainer = "iihqkc0";
|
|
16
17
|
var handleContainerVisible = "iihqkc1";
|
|
17
18
|
var handleBtn = "iihqkc2";
|
|
18
19
|
var draggingBlock = "iihqkc3";
|
|
19
20
|
var dragPreview = "iihqkc4";
|
|
20
|
-
var dropIndicator = "iihqkc5";
|
|
21
21
|
var blockSelected = "iihqkc6";
|
|
22
22
|
var dragCountBadge = "iihqkc7";
|
|
23
23
|
var menuItemDestructive = "iihqkc8";
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/blockSelectionUtils.ts
|
|
26
|
+
var PASTEABLE_CLIPBOARD_TYPES = [
|
|
27
|
+
"application/x-lexical-editor",
|
|
28
|
+
"text/html",
|
|
29
|
+
"text/plain",
|
|
30
|
+
"text/uri-list"
|
|
29
31
|
];
|
|
30
32
|
function serializeNodeWithChildren(node) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
return serialized;
|
|
33
|
+
const serialized = node.exportJSON();
|
|
34
|
+
if ($isElementNode(node)) serialized.children = node.getChildren().map(serializeNodeWithChildren);
|
|
35
|
+
return serialized;
|
|
36
36
|
}
|
|
37
37
|
function getHtmlFromTopLevelNodes(editor, nodes) {
|
|
38
|
-
|
|
38
|
+
return nodes.map((node) => editor.getElementByKey(node.getKey())?.outerHTML ?? "").filter(Boolean).join("\n");
|
|
39
39
|
}
|
|
40
40
|
function setBlockClipboardDataTransfer(dataTransfer, clipboardData) {
|
|
41
|
-
|
|
42
|
-
dataTransfer.setData(mimeType, value);
|
|
43
|
-
}
|
|
41
|
+
for (const [mimeType, value] of Object.entries(clipboardData)) dataTransfer.setData(mimeType, value);
|
|
44
42
|
}
|
|
45
43
|
function createDataTransferFromBlockClipboardData(clipboardData) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
44
|
+
return {
|
|
45
|
+
files: { length: 0 },
|
|
46
|
+
get types() {
|
|
47
|
+
return Object.keys(clipboardData);
|
|
48
|
+
},
|
|
49
|
+
getData(type) {
|
|
50
|
+
return clipboardData[type] ?? "";
|
|
51
|
+
},
|
|
52
|
+
setData(type, value) {
|
|
53
|
+
clipboardData[type] = value;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
58
56
|
}
|
|
59
57
|
async function readNativeClipboardDataTransfer() {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (!clipboardData["text/plain"] && typeof clipboard.readText === "function") {
|
|
77
|
-
try {
|
|
78
|
-
const text = await clipboard.readText();
|
|
79
|
-
if (text) {
|
|
80
|
-
clipboardData["text/plain"] = text;
|
|
81
|
-
}
|
|
82
|
-
} catch {
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
const dataTransfer = createDataTransferFromBlockClipboardData(clipboardData);
|
|
86
|
-
return hasInsertableClipboardData(dataTransfer) ? dataTransfer : null;
|
|
58
|
+
const clipboard = globalThis.navigator?.clipboard;
|
|
59
|
+
if (!clipboard) return null;
|
|
60
|
+
const clipboardData = {};
|
|
61
|
+
if (typeof clipboard.read === "function") try {
|
|
62
|
+
const items = await clipboard.read();
|
|
63
|
+
for (const item of items) for (const type of PASTEABLE_CLIPBOARD_TYPES) {
|
|
64
|
+
if (!item.types.includes(type)) continue;
|
|
65
|
+
clipboardData[type] = await (await item.getType(type)).text();
|
|
66
|
+
}
|
|
67
|
+
} catch {}
|
|
68
|
+
if (!clipboardData["text/plain"] && typeof clipboard.readText === "function") try {
|
|
69
|
+
const text = await clipboard.readText();
|
|
70
|
+
if (text) clipboardData["text/plain"] = text;
|
|
71
|
+
} catch {}
|
|
72
|
+
const dataTransfer = createDataTransferFromBlockClipboardData(clipboardData);
|
|
73
|
+
return hasInsertableClipboardData(dataTransfer) ? dataTransfer : null;
|
|
87
74
|
}
|
|
88
75
|
function writeBlockClipboardDataToNativeClipboard(editor, clipboardData) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
76
|
+
const ownerDocument = editor.getRootElement()?.ownerDocument ?? globalThis.document;
|
|
77
|
+
if (!ownerDocument?.execCommand) return false;
|
|
78
|
+
let wrote = false;
|
|
79
|
+
const onCopy = (event) => {
|
|
80
|
+
if (!event.clipboardData) return;
|
|
81
|
+
event.preventDefault();
|
|
82
|
+
event.stopImmediatePropagation();
|
|
83
|
+
setBlockClipboardDataTransfer(event.clipboardData, clipboardData);
|
|
84
|
+
wrote = true;
|
|
85
|
+
};
|
|
86
|
+
ownerDocument.addEventListener("copy", onCopy, true);
|
|
87
|
+
try {
|
|
88
|
+
return ownerDocument.execCommand("copy") && wrote;
|
|
89
|
+
} finally {
|
|
90
|
+
ownerDocument.removeEventListener("copy", onCopy, true);
|
|
91
|
+
}
|
|
106
92
|
}
|
|
107
93
|
function selectTopLevelNode(node, placement) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const selection = $createNodeSelection();
|
|
117
|
-
selection.add(node.getKey());
|
|
118
|
-
$setSelection(selection);
|
|
94
|
+
if ($isElementNode(node)) {
|
|
95
|
+
if (placement === "end") node.selectEnd();
|
|
96
|
+
else node.selectStart();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const selection = $createNodeSelection();
|
|
100
|
+
selection.add(node.getKey());
|
|
101
|
+
$setSelection(selection);
|
|
119
102
|
}
|
|
120
103
|
function buildBlockClipboardData(editor, nodes) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
};
|
|
104
|
+
const namespace = editor._config.namespace;
|
|
105
|
+
const serializedNodes = nodes.map(serializeNodeWithChildren);
|
|
106
|
+
const html = getHtmlFromTopLevelNodes(editor, nodes);
|
|
107
|
+
return {
|
|
108
|
+
"application/x-lexical-editor": JSON.stringify({
|
|
109
|
+
namespace,
|
|
110
|
+
nodes: serializedNodes
|
|
111
|
+
}),
|
|
112
|
+
"text/html": html,
|
|
113
|
+
"text/plain": nodes.map((node) => node.getTextContent()).join("\n\n")
|
|
114
|
+
};
|
|
133
115
|
}
|
|
134
116
|
function removeTopLevelNodesAndRestoreSelection(nodes) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const fallbackNode = root.getFirstChild();
|
|
159
|
-
if (fallbackNode) {
|
|
160
|
-
selectTopLevelNode(fallbackNode, "start");
|
|
161
|
-
}
|
|
117
|
+
if (nodes.length === 0) return;
|
|
118
|
+
const firstNode = nodes[0];
|
|
119
|
+
const lastNode = nodes.at(-1);
|
|
120
|
+
const previousSibling = firstNode.getPreviousSibling();
|
|
121
|
+
const nextSibling = lastNode.getNextSibling();
|
|
122
|
+
for (const node of nodes) node.remove();
|
|
123
|
+
const root = $getRoot();
|
|
124
|
+
if (root.getChildrenSize() === 0) {
|
|
125
|
+
const paragraph = $createParagraphNode();
|
|
126
|
+
root.append(paragraph);
|
|
127
|
+
paragraph.selectStart();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (nextSibling) {
|
|
131
|
+
selectTopLevelNode(nextSibling, "start");
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (previousSibling) {
|
|
135
|
+
selectTopLevelNode(previousSibling, "end");
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const fallbackNode = root.getFirstChild();
|
|
139
|
+
if (fallbackNode) selectTopLevelNode(fallbackNode, "start");
|
|
162
140
|
}
|
|
163
141
|
function removeTopLevelNodesAndCreatePasteTarget(nodes) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
pasteTarget.selectStart();
|
|
142
|
+
if (nodes.length === 0) return;
|
|
143
|
+
const pasteTarget = $createParagraphNode();
|
|
144
|
+
nodes[0].insertBefore(pasteTarget);
|
|
145
|
+
for (const node of nodes) node.remove();
|
|
146
|
+
pasteTarget.selectStart();
|
|
171
147
|
}
|
|
172
148
|
function getDataTransferFromPasteEvent(event) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
149
|
+
if (!event || typeof event !== "object") return null;
|
|
150
|
+
const pasteEvent = event;
|
|
151
|
+
return pasteEvent.clipboardData ?? pasteEvent.dataTransfer ?? null;
|
|
176
152
|
}
|
|
177
153
|
function hasPasteableClipboardData(clipboardData) {
|
|
178
|
-
|
|
179
|
-
|
|
154
|
+
if (clipboardData.files.length > 0) return true;
|
|
155
|
+
return hasInsertableClipboardData(clipboardData);
|
|
180
156
|
}
|
|
181
157
|
function hasInsertableClipboardData(clipboardData) {
|
|
182
|
-
|
|
158
|
+
return PASTEABLE_CLIPBOARD_TYPES.some((type) => clipboardData.getData(type).length > 0);
|
|
183
159
|
}
|
|
184
160
|
function isDataTransferOnlyPasteEvent(event) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
161
|
+
if (!event || typeof event !== "object") return false;
|
|
162
|
+
const pasteEvent = event;
|
|
163
|
+
return !pasteEvent.clipboardData && Boolean(pasteEvent.dataTransfer);
|
|
188
164
|
}
|
|
189
165
|
function insertPlainText(text) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
currentSelection.insertText(part);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
return true;
|
|
166
|
+
const selection = $getSelection();
|
|
167
|
+
if (!selection) return false;
|
|
168
|
+
if (!$isRangeSelection(selection)) {
|
|
169
|
+
selection.insertRawText(text);
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
const parts = text.split(/(\r?\n|\t)/);
|
|
173
|
+
if (parts.at(-1) === "") parts.pop();
|
|
174
|
+
for (const part of parts) {
|
|
175
|
+
const currentSelection = $getSelection();
|
|
176
|
+
if (!$isRangeSelection(currentSelection)) continue;
|
|
177
|
+
if (part === "\n" || part === "\r\n") currentSelection.insertParagraph();
|
|
178
|
+
else if (part === " ") currentSelection.insertNodes([$createTabNode()]);
|
|
179
|
+
else currentSelection.insertText(part);
|
|
180
|
+
}
|
|
181
|
+
return true;
|
|
210
182
|
}
|
|
211
183
|
function insertDataTransferForBlockSelectionPaste(editor, dataTransfer) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
return true;
|
|
235
|
-
} catch {
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
const text = plainString || dataTransfer.getData("text/uri-list");
|
|
239
|
-
return text ? insertPlainText(text) : false;
|
|
184
|
+
const lexicalString = dataTransfer.getData("application/x-lexical-editor");
|
|
185
|
+
if (lexicalString) try {
|
|
186
|
+
const payload = JSON.parse(lexicalString);
|
|
187
|
+
const lexicalEditor = editor;
|
|
188
|
+
if (payload.namespace === lexicalEditor._config.namespace && Array.isArray(payload.nodes)) {
|
|
189
|
+
const selection = $getSelection();
|
|
190
|
+
if (!selection) return false;
|
|
191
|
+
selection.insertNodes(payload.nodes.map($parseSerializedNode));
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
} catch {}
|
|
195
|
+
const htmlString = dataTransfer.getData("text/html");
|
|
196
|
+
const plainString = dataTransfer.getData("text/plain");
|
|
197
|
+
if (htmlString && plainString !== htmlString) try {
|
|
198
|
+
const selection = $getSelection();
|
|
199
|
+
if (!selection) return false;
|
|
200
|
+
const dom = new DOMParser().parseFromString(htmlString, "text/html");
|
|
201
|
+
selection.insertNodes($generateNodesFromDOM(editor, dom));
|
|
202
|
+
return true;
|
|
203
|
+
} catch {}
|
|
204
|
+
const text = plainString || dataTransfer.getData("text/uri-list");
|
|
205
|
+
return text ? insertPlainText(text) : false;
|
|
240
206
|
}
|
|
241
207
|
function replaceTopLevelNodesWithDataTransfer(editor, nodes, dataTransfer) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
208
|
+
if (nodes.length === 0 || !hasInsertableClipboardData(dataTransfer)) return false;
|
|
209
|
+
removeTopLevelNodesAndCreatePasteTarget(nodes);
|
|
210
|
+
return insertDataTransferForBlockSelectionPaste(editor, dataTransfer);
|
|
245
211
|
}
|
|
212
|
+
//#endregion
|
|
213
|
+
//#region src/useBlockSelection.ts
|
|
246
214
|
function isPasteBeforeInputEvent(event) {
|
|
247
|
-
|
|
215
|
+
return event.inputType === "insertFromPaste" || event.inputType === "insertFromPasteAsQuotation";
|
|
248
216
|
}
|
|
249
217
|
function $getTopLevelKeys() {
|
|
250
|
-
|
|
218
|
+
return $getRoot().getChildren().map((c) => c.getKey());
|
|
251
219
|
}
|
|
252
220
|
function getTopLevelKey(node) {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
return current?.getParent() === $getRoot() ? current.getKey() : null;
|
|
221
|
+
let current = node;
|
|
222
|
+
while (current && current.getParent() && current.getParent() !== $getRoot()) current = current.getParent();
|
|
223
|
+
return current?.getParent() === $getRoot() ? current.getKey() : null;
|
|
258
224
|
}
|
|
259
225
|
function $selectBlockRange(anchorKey, focusKey) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
}
|
|
270
|
-
$setSelection(sel);
|
|
226
|
+
const children = $getRoot().getChildren();
|
|
227
|
+
const anchorIdx = children.findIndex((c) => c.getKey() === anchorKey);
|
|
228
|
+
const focusIdx = children.findIndex((c) => c.getKey() === focusKey);
|
|
229
|
+
if (anchorIdx === -1 || focusIdx === -1) return;
|
|
230
|
+
const start = Math.min(anchorIdx, focusIdx);
|
|
231
|
+
const end = Math.max(anchorIdx, focusIdx);
|
|
232
|
+
const sel = $createNodeSelection();
|
|
233
|
+
for (let i = start; i <= end; i++) sel.add(children[i].getKey());
|
|
234
|
+
$setSelection(sel);
|
|
271
235
|
}
|
|
272
236
|
function useBlockSelection(editor) {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
(event) => {
|
|
657
|
-
const clipboardData = getDataTransferFromPasteEvent(event);
|
|
658
|
-
if (!clipboardData) {
|
|
659
|
-
return false;
|
|
660
|
-
}
|
|
661
|
-
const dataTransferOnly = isDataTransferOnlyPasteEvent(event);
|
|
662
|
-
const hasPasteData = dataTransferOnly ? hasInsertableClipboardData(clipboardData) : hasPasteableClipboardData(clipboardData);
|
|
663
|
-
if (!hasPasteData) {
|
|
664
|
-
return false;
|
|
665
|
-
}
|
|
666
|
-
const nodes = getOwnedSelectionNodes();
|
|
667
|
-
if (nodes.length === 0) {
|
|
668
|
-
return false;
|
|
669
|
-
}
|
|
670
|
-
removeTopLevelNodesAndCreatePasteTarget(nodes);
|
|
671
|
-
clearBlockSelectionState();
|
|
672
|
-
if (dataTransferOnly) {
|
|
673
|
-
if (event && typeof event === "object" && "preventDefault" in event && typeof event.preventDefault === "function") {
|
|
674
|
-
event.preventDefault();
|
|
675
|
-
}
|
|
676
|
-
return insertDataTransferForBlockSelectionPaste(editor, clipboardData);
|
|
677
|
-
}
|
|
678
|
-
return false;
|
|
679
|
-
},
|
|
680
|
-
COMMAND_PRIORITY_CRITICAL
|
|
681
|
-
);
|
|
682
|
-
const handleDeleteBlocks = (payload) => {
|
|
683
|
-
let keysToDelete = [];
|
|
684
|
-
editor.getEditorState().read(() => {
|
|
685
|
-
keysToDelete = getOwnedSelectionNodes().map((node) => node.getKey());
|
|
686
|
-
});
|
|
687
|
-
if (keysToDelete.length === 0) return false;
|
|
688
|
-
if (payload && typeof payload !== "boolean" && "preventDefault" in payload && typeof payload.preventDefault === "function") {
|
|
689
|
-
payload.preventDefault();
|
|
690
|
-
}
|
|
691
|
-
deleteBlocksByKeys(keysToDelete);
|
|
692
|
-
return true;
|
|
693
|
-
};
|
|
694
|
-
const unregBackspace = editor.registerCommand(
|
|
695
|
-
KEY_BACKSPACE_COMMAND,
|
|
696
|
-
handleDeleteBlocks,
|
|
697
|
-
COMMAND_PRIORITY_CRITICAL
|
|
698
|
-
);
|
|
699
|
-
const unregDelete = editor.registerCommand(
|
|
700
|
-
KEY_DELETE_COMMAND,
|
|
701
|
-
handleDeleteBlocks,
|
|
702
|
-
COMMAND_PRIORITY_CRITICAL
|
|
703
|
-
);
|
|
704
|
-
const unregRemoveText = editor.registerCommand(
|
|
705
|
-
REMOVE_TEXT_COMMAND,
|
|
706
|
-
handleDeleteBlocks,
|
|
707
|
-
COMMAND_PRIORITY_CRITICAL
|
|
708
|
-
);
|
|
709
|
-
const unregDeleteCharacter = editor.registerCommand(
|
|
710
|
-
DELETE_CHARACTER_COMMAND,
|
|
711
|
-
handleDeleteBlocks,
|
|
712
|
-
COMMAND_PRIORITY_CRITICAL
|
|
713
|
-
);
|
|
714
|
-
return () => {
|
|
715
|
-
unregKeyDown();
|
|
716
|
-
unregShiftDown();
|
|
717
|
-
unregShiftUp();
|
|
718
|
-
unregSelectAll();
|
|
719
|
-
unregEscape();
|
|
720
|
-
unregCopy();
|
|
721
|
-
unregCut();
|
|
722
|
-
unregPaste();
|
|
723
|
-
unregBackspace();
|
|
724
|
-
unregDelete();
|
|
725
|
-
unregRemoveText();
|
|
726
|
-
unregDeleteCharacter();
|
|
727
|
-
};
|
|
728
|
-
}, [
|
|
729
|
-
clearBlockSelectionState,
|
|
730
|
-
deleteBlocksByKeys,
|
|
731
|
-
editor,
|
|
732
|
-
getCurrentBlockClipboardData,
|
|
733
|
-
getOwnedSelectionNodes,
|
|
734
|
-
replaceBlockSelectionWithDataTransfer
|
|
735
|
-
]);
|
|
736
|
-
const selectBlock = useCallback(
|
|
737
|
-
(nodeKey, shiftKey) => {
|
|
738
|
-
editor.update(() => {
|
|
739
|
-
if (shiftKey && anchorKeyRef.current) {
|
|
740
|
-
focusKeyRef.current = nodeKey;
|
|
741
|
-
$selectBlockRange(anchorKeyRef.current, nodeKey);
|
|
742
|
-
const children = $getRoot().getChildren();
|
|
743
|
-
const anchorIdx = children.findIndex((c) => c.getKey() === anchorKeyRef.current);
|
|
744
|
-
const focusIdx = children.findIndex((c) => c.getKey() === nodeKey);
|
|
745
|
-
if (anchorIdx !== -1 && focusIdx !== -1) {
|
|
746
|
-
const start = Math.min(anchorIdx, focusIdx);
|
|
747
|
-
const end = Math.max(anchorIdx, focusIdx);
|
|
748
|
-
blockSelectionKeysRef.current = new Set(
|
|
749
|
-
children.slice(start, end + 1).map((c) => c.getKey())
|
|
750
|
-
);
|
|
751
|
-
}
|
|
752
|
-
} else {
|
|
753
|
-
anchorKeyRef.current = nodeKey;
|
|
754
|
-
focusKeyRef.current = nodeKey;
|
|
755
|
-
blockSelectionKeysRef.current = /* @__PURE__ */ new Set([nodeKey]);
|
|
756
|
-
const sel = $createNodeSelection();
|
|
757
|
-
sel.add(nodeKey);
|
|
758
|
-
$setSelection(sel);
|
|
759
|
-
}
|
|
760
|
-
});
|
|
761
|
-
},
|
|
762
|
-
[editor]
|
|
763
|
-
);
|
|
764
|
-
const getSelectedKeys = useCallback(() => {
|
|
765
|
-
if (blockSelectionKeysRef.current.size === 0) return [];
|
|
766
|
-
let keys = [];
|
|
767
|
-
editor.getEditorState().read(() => {
|
|
768
|
-
keys = getTopLevelNodesByKeys(blockSelectionKeysRef.current).map((node) => node.getKey());
|
|
769
|
-
});
|
|
770
|
-
return keys;
|
|
771
|
-
}, [editor, getTopLevelNodesByKeys]);
|
|
772
|
-
const deleteSelectedBlocks = useCallback(
|
|
773
|
-
(fallbackNodeKey) => {
|
|
774
|
-
const selectedKeys = getSelectedKeys();
|
|
775
|
-
const keys = selectedKeys.length > 0 ? selectedKeys : fallbackNodeKey ? [fallbackNodeKey] : [];
|
|
776
|
-
if (keys.length === 0) return;
|
|
777
|
-
deleteBlocksByKeys(keys);
|
|
778
|
-
},
|
|
779
|
-
[deleteBlocksByKeys, getSelectedKeys]
|
|
780
|
-
);
|
|
781
|
-
const isBlockSelectionActive = useCallback(
|
|
782
|
-
() => blockSelectionKeysRef.current.size > 0,
|
|
783
|
-
[]
|
|
784
|
-
);
|
|
785
|
-
return { selectBlock, getSelectedKeys, isBlockSelectionActive, deleteSelectedBlocks };
|
|
237
|
+
const anchorKeyRef = useRef(null);
|
|
238
|
+
const focusKeyRef = useRef(null);
|
|
239
|
+
const blockSelectionKeysRef = useRef(/* @__PURE__ */ new Set());
|
|
240
|
+
const latestBlockClipboardDataRef = useRef(null);
|
|
241
|
+
const clearBlockSelectionState = useCallback(() => {
|
|
242
|
+
blockSelectionKeysRef.current = /* @__PURE__ */ new Set();
|
|
243
|
+
anchorKeyRef.current = null;
|
|
244
|
+
focusKeyRef.current = null;
|
|
245
|
+
}, []);
|
|
246
|
+
const getTopLevelNodesByKeys = useCallback((keys) => {
|
|
247
|
+
if (keys.size === 0) return [];
|
|
248
|
+
return $getRoot().getChildren().filter((node) => keys.has(node.getKey()));
|
|
249
|
+
}, []);
|
|
250
|
+
const getOwnedSelectionNodes = useCallback(() => {
|
|
251
|
+
const ownedKeys = blockSelectionKeysRef.current;
|
|
252
|
+
if (ownedKeys.size === 0) {
|
|
253
|
+
const selection = $getSelection();
|
|
254
|
+
if (!$isNodeSelection(selection)) return [];
|
|
255
|
+
const selectedNodes = selection.getNodes();
|
|
256
|
+
const topLevelFallbackNodes = selectedNodes.filter((node) => node.getParent() === $getRoot());
|
|
257
|
+
if (topLevelFallbackNodes.length > 1 && topLevelFallbackNodes.length === selectedNodes.length) return getTopLevelNodesByKeys(new Set(topLevelFallbackNodes.map((node) => node.getKey())));
|
|
258
|
+
return [];
|
|
259
|
+
}
|
|
260
|
+
if (!$isNodeSelection($getSelection())) return getTopLevelNodesByKeys(ownedKeys);
|
|
261
|
+
return getTopLevelNodesByKeys(ownedKeys);
|
|
262
|
+
}, [getTopLevelNodesByKeys]);
|
|
263
|
+
const replaceBlockSelectionWithDataTransfer = useCallback((dataTransfer) => {
|
|
264
|
+
if (!hasInsertableClipboardData(dataTransfer)) return false;
|
|
265
|
+
let handled = false;
|
|
266
|
+
editor.update(() => {
|
|
267
|
+
const nodes = getOwnedSelectionNodes();
|
|
268
|
+
if (nodes.length === 0) return;
|
|
269
|
+
handled = replaceTopLevelNodesWithDataTransfer(editor, nodes, dataTransfer);
|
|
270
|
+
if (handled) clearBlockSelectionState();
|
|
271
|
+
}, {
|
|
272
|
+
discrete: true,
|
|
273
|
+
tag: PASTE_TAG
|
|
274
|
+
});
|
|
275
|
+
return handled;
|
|
276
|
+
}, [
|
|
277
|
+
clearBlockSelectionState,
|
|
278
|
+
editor,
|
|
279
|
+
getOwnedSelectionNodes
|
|
280
|
+
]);
|
|
281
|
+
const getCurrentBlockClipboardData = useCallback(() => {
|
|
282
|
+
let clipboardData = null;
|
|
283
|
+
editor.getEditorState().read(() => {
|
|
284
|
+
const nodes = getOwnedSelectionNodes();
|
|
285
|
+
if (nodes.length === 0) return;
|
|
286
|
+
clipboardData = buildBlockClipboardData(editor, nodes);
|
|
287
|
+
});
|
|
288
|
+
return clipboardData;
|
|
289
|
+
}, [editor, getOwnedSelectionNodes]);
|
|
290
|
+
const deleteBlocksByKeys = useCallback((keys) => {
|
|
291
|
+
editor.update(() => {
|
|
292
|
+
const nodes = getTopLevelNodesByKeys(new Set(keys));
|
|
293
|
+
if (nodes.length === 0) return;
|
|
294
|
+
removeTopLevelNodesAndRestoreSelection(nodes);
|
|
295
|
+
clearBlockSelectionState();
|
|
296
|
+
}, { discrete: true });
|
|
297
|
+
}, [
|
|
298
|
+
clearBlockSelectionState,
|
|
299
|
+
editor,
|
|
300
|
+
getTopLevelNodesByKeys
|
|
301
|
+
]);
|
|
302
|
+
useEffect(() => {
|
|
303
|
+
let prevKeys = /* @__PURE__ */ new Set();
|
|
304
|
+
const unregister = editor.registerUpdateListener(({ editorState }) => {
|
|
305
|
+
if (!editor.getRootElement()) return;
|
|
306
|
+
const nextKeys = /* @__PURE__ */ new Set();
|
|
307
|
+
let isNodeSel = false;
|
|
308
|
+
const rangeTopLevelKeys = [];
|
|
309
|
+
let topLevelKeys = [];
|
|
310
|
+
editorState.read(() => {
|
|
311
|
+
const sel = $getSelection();
|
|
312
|
+
if ($isNodeSelection(sel)) {
|
|
313
|
+
isNodeSel = true;
|
|
314
|
+
for (const node of sel.getNodes()) nextKeys.add(node.getKey());
|
|
315
|
+
} else if ($isRangeSelection(sel)) {
|
|
316
|
+
const anchorTopLevelKey = getTopLevelKey(sel.anchor.getNode());
|
|
317
|
+
const focusTopLevelKey = getTopLevelKey(sel.focus.getNode());
|
|
318
|
+
if (anchorTopLevelKey) rangeTopLevelKeys.push(anchorTopLevelKey);
|
|
319
|
+
if (focusTopLevelKey && focusTopLevelKey !== anchorTopLevelKey) rangeTopLevelKeys.push(focusTopLevelKey);
|
|
320
|
+
}
|
|
321
|
+
topLevelKeys = $getTopLevelKeys();
|
|
322
|
+
});
|
|
323
|
+
if (blockSelectionKeysRef.current.size > 0) if (!isNodeSel) {
|
|
324
|
+
const owned = blockSelectionKeysRef.current;
|
|
325
|
+
if (!(rangeTopLevelKeys.length > 0 && rangeTopLevelKeys.every((key) => owned.has(key)))) clearBlockSelectionState();
|
|
326
|
+
} else {
|
|
327
|
+
const owned = blockSelectionKeysRef.current;
|
|
328
|
+
if (!(nextKeys.size === owned.size && [...nextKeys].every((k) => owned.has(k)))) {
|
|
329
|
+
const topLevelSet = new Set(topLevelKeys);
|
|
330
|
+
const restoredTopLevel = [...nextKeys].filter((k) => topLevelSet.has(k));
|
|
331
|
+
if (restoredTopLevel.length > 1) {
|
|
332
|
+
const indices = restoredTopLevel.map((k) => topLevelKeys.indexOf(k)).sort((a, b) => a - b);
|
|
333
|
+
anchorKeyRef.current = topLevelKeys[indices[0]];
|
|
334
|
+
focusKeyRef.current = topLevelKeys[indices.at(-1)];
|
|
335
|
+
blockSelectionKeysRef.current = new Set(restoredTopLevel);
|
|
336
|
+
} else clearBlockSelectionState();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
const topLevelKeySet = new Set(topLevelKeys);
|
|
340
|
+
const highlightKeys = blockSelectionKeysRef.current.size > 0 ? new Set([...blockSelectionKeysRef.current].filter((key) => topLevelKeySet.has(key))) : /* @__PURE__ */ new Set();
|
|
341
|
+
for (const key of prevKeys) if (!highlightKeys.has(key)) editor.getElementByKey(key)?.classList.remove(blockSelected);
|
|
342
|
+
for (const key of highlightKeys) if (!prevKeys.has(key)) editor.getElementByKey(key)?.classList.add(blockSelected);
|
|
343
|
+
prevKeys = highlightKeys;
|
|
344
|
+
});
|
|
345
|
+
return () => {
|
|
346
|
+
unregister();
|
|
347
|
+
for (const key of prevKeys) editor.getElementByKey(key)?.classList.remove(blockSelected);
|
|
348
|
+
};
|
|
349
|
+
}, [clearBlockSelectionState, editor]);
|
|
350
|
+
useEffect(() => {
|
|
351
|
+
const rootEl = editor.getRootElement();
|
|
352
|
+
if (!rootEl) return;
|
|
353
|
+
const onPointerDown = (event) => {
|
|
354
|
+
if (blockSelectionKeysRef.current.size === 0) return;
|
|
355
|
+
if (!(event.target instanceof Node) || !rootEl.contains(event.target)) return;
|
|
356
|
+
clearBlockSelectionState();
|
|
357
|
+
};
|
|
358
|
+
rootEl.addEventListener("pointerdown", onPointerDown, true);
|
|
359
|
+
return () => rootEl.removeEventListener("pointerdown", onPointerDown, true);
|
|
360
|
+
}, [clearBlockSelectionState, editor]);
|
|
361
|
+
useEffect(() => {
|
|
362
|
+
const rootEl = editor.getRootElement();
|
|
363
|
+
if (!rootEl) return;
|
|
364
|
+
const onFocusIn = (e) => {
|
|
365
|
+
if (blockSelectionKeysRef.current.size === 0) return;
|
|
366
|
+
const target = e.target;
|
|
367
|
+
if (!target) return;
|
|
368
|
+
const nestedEditable = target.closest("[contenteditable=\"true\"]");
|
|
369
|
+
if (nestedEditable && nestedEditable !== rootEl) {
|
|
370
|
+
clearBlockSelectionState();
|
|
371
|
+
editor.update(() => {
|
|
372
|
+
if ($isNodeSelection($getSelection())) $setSelection(null);
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
rootEl.addEventListener("focusin", onFocusIn);
|
|
377
|
+
return () => rootEl.removeEventListener("focusin", onFocusIn);
|
|
378
|
+
}, [clearBlockSelectionState, editor]);
|
|
379
|
+
useEffect(() => {
|
|
380
|
+
const rootEl = editor.getRootElement();
|
|
381
|
+
if (!rootEl) return;
|
|
382
|
+
const onBeforeInput = (event) => {
|
|
383
|
+
if (!isPasteBeforeInputEvent(event)) return;
|
|
384
|
+
if (blockSelectionKeysRef.current.size === 0 || !event.dataTransfer) return;
|
|
385
|
+
if (!replaceBlockSelectionWithDataTransfer(event.dataTransfer)) return;
|
|
386
|
+
event.preventDefault();
|
|
387
|
+
event.stopPropagation();
|
|
388
|
+
};
|
|
389
|
+
rootEl.addEventListener("beforeinput", onBeforeInput, true);
|
|
390
|
+
return () => rootEl.removeEventListener("beforeinput", onBeforeInput, true);
|
|
391
|
+
}, [editor, replaceBlockSelectionWithDataTransfer]);
|
|
392
|
+
useEffect(() => {
|
|
393
|
+
const unregKeyDown = editor.registerCommand(KEY_DOWN_COMMAND, (event) => {
|
|
394
|
+
const key = event.key.toLowerCase();
|
|
395
|
+
if (!event.metaKey && !event.ctrlKey && key !== "escape") return false;
|
|
396
|
+
if (![
|
|
397
|
+
"a",
|
|
398
|
+
"c",
|
|
399
|
+
"v",
|
|
400
|
+
"x",
|
|
401
|
+
"escape"
|
|
402
|
+
].includes(key)) return false;
|
|
403
|
+
if ((event.metaKey || event.ctrlKey) && key === "v" && blockSelectionKeysRef.current.size > 0) {
|
|
404
|
+
event.preventDefault();
|
|
405
|
+
const cachedClipboardData = latestBlockClipboardDataRef.current ? { ...latestBlockClipboardDataRef.current } : null;
|
|
406
|
+
(async () => {
|
|
407
|
+
const dataTransfer = cachedClipboardData ? createDataTransferFromBlockClipboardData(cachedClipboardData) : await readNativeClipboardDataTransfer();
|
|
408
|
+
if (!dataTransfer) return;
|
|
409
|
+
if (blockSelectionKeysRef.current.size === 0) return;
|
|
410
|
+
replaceBlockSelectionWithDataTransfer(dataTransfer);
|
|
411
|
+
})();
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
return false;
|
|
415
|
+
}, COMMAND_PRIORITY_CRITICAL);
|
|
416
|
+
const unregShiftDown = editor.registerCommand(KEY_ARROW_DOWN_COMMAND, (event) => {
|
|
417
|
+
if (!event?.shiftKey) return false;
|
|
418
|
+
if (blockSelectionKeysRef.current.size === 0 || !focusKeyRef.current) return false;
|
|
419
|
+
if (!$isNodeSelection($getSelection())) return false;
|
|
420
|
+
const children = $getRoot().getChildren();
|
|
421
|
+
const focusIdx = children.findIndex((c) => c.getKey() === focusKeyRef.current);
|
|
422
|
+
if (focusIdx === -1 || focusIdx >= children.length - 1) return false;
|
|
423
|
+
event.preventDefault();
|
|
424
|
+
focusKeyRef.current = children[focusIdx + 1].getKey();
|
|
425
|
+
$selectBlockRange(anchorKeyRef.current, focusKeyRef.current);
|
|
426
|
+
const start = Math.min(children.findIndex((c) => c.getKey() === anchorKeyRef.current), focusIdx + 1);
|
|
427
|
+
const end = Math.max(children.findIndex((c) => c.getKey() === anchorKeyRef.current), focusIdx + 1);
|
|
428
|
+
blockSelectionKeysRef.current = new Set(children.slice(start, end + 1).map((c) => c.getKey()));
|
|
429
|
+
return true;
|
|
430
|
+
}, COMMAND_PRIORITY_CRITICAL);
|
|
431
|
+
const unregShiftUp = editor.registerCommand(KEY_ARROW_UP_COMMAND, (event) => {
|
|
432
|
+
if (!event?.shiftKey) return false;
|
|
433
|
+
if (blockSelectionKeysRef.current.size === 0 || !focusKeyRef.current) return false;
|
|
434
|
+
if (!$isNodeSelection($getSelection())) return false;
|
|
435
|
+
const children = $getRoot().getChildren();
|
|
436
|
+
const focusIdx = children.findIndex((c) => c.getKey() === focusKeyRef.current);
|
|
437
|
+
if (focusIdx === -1 || focusIdx <= 0) return false;
|
|
438
|
+
event.preventDefault();
|
|
439
|
+
focusKeyRef.current = children[focusIdx - 1].getKey();
|
|
440
|
+
$selectBlockRange(anchorKeyRef.current, focusKeyRef.current);
|
|
441
|
+
const anchorIdx = children.findIndex((c) => c.getKey() === anchorKeyRef.current);
|
|
442
|
+
const start = Math.min(anchorIdx, focusIdx - 1);
|
|
443
|
+
const end = Math.max(anchorIdx, focusIdx - 1);
|
|
444
|
+
blockSelectionKeysRef.current = new Set(children.slice(start, end + 1).map((c) => c.getKey()));
|
|
445
|
+
return true;
|
|
446
|
+
}, COMMAND_PRIORITY_CRITICAL);
|
|
447
|
+
const unregSelectAll = editor.registerCommand(SELECT_ALL_COMMAND, () => {
|
|
448
|
+
if (blockSelectionKeysRef.current.size > 0) {
|
|
449
|
+
const allKeys = $getRoot().getChildren().map((c) => c.getKey());
|
|
450
|
+
if (blockSelectionKeysRef.current.size >= allKeys.length) return true;
|
|
451
|
+
anchorKeyRef.current = allKeys[0];
|
|
452
|
+
focusKeyRef.current = allKeys.at(-1);
|
|
453
|
+
blockSelectionKeysRef.current = new Set(allKeys);
|
|
454
|
+
const nodeSel = $createNodeSelection();
|
|
455
|
+
for (const key of allKeys) nodeSel.add(key);
|
|
456
|
+
$setSelection(nodeSel);
|
|
457
|
+
return true;
|
|
458
|
+
}
|
|
459
|
+
const sel = $getSelection();
|
|
460
|
+
let topLevelKey = null;
|
|
461
|
+
if ($isRangeSelection(sel)) {
|
|
462
|
+
let node = sel.anchor.getNode();
|
|
463
|
+
while (node.getParent() && node.getParent() !== $getRoot()) node = node.getParent();
|
|
464
|
+
if (node.getParent() === $getRoot()) topLevelKey = node.getKey();
|
|
465
|
+
} else if ($isNodeSelection(sel)) {
|
|
466
|
+
const nodes = sel.getNodes();
|
|
467
|
+
if (nodes.length > 0) {
|
|
468
|
+
let node = nodes[0];
|
|
469
|
+
while (node.getParent() && node.getParent() !== $getRoot()) node = node.getParent();
|
|
470
|
+
if (node.getParent() === $getRoot()) topLevelKey = node.getKey();
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (topLevelKey) {
|
|
474
|
+
anchorKeyRef.current = topLevelKey;
|
|
475
|
+
focusKeyRef.current = topLevelKey;
|
|
476
|
+
blockSelectionKeysRef.current = new Set([topLevelKey]);
|
|
477
|
+
const nodeSel = $createNodeSelection();
|
|
478
|
+
nodeSel.add(topLevelKey);
|
|
479
|
+
$setSelection(nodeSel);
|
|
480
|
+
return true;
|
|
481
|
+
}
|
|
482
|
+
return false;
|
|
483
|
+
}, COMMAND_PRIORITY_HIGH);
|
|
484
|
+
const unregEscape = editor.registerCommand(KEY_ESCAPE_COMMAND, () => {
|
|
485
|
+
if (blockSelectionKeysRef.current.size === 0) return false;
|
|
486
|
+
clearBlockSelectionState();
|
|
487
|
+
$setSelection(null);
|
|
488
|
+
return true;
|
|
489
|
+
}, COMMAND_PRIORITY_HIGH);
|
|
490
|
+
const unregCopy = editor.registerCommand(COPY_COMMAND, (event) => {
|
|
491
|
+
const clipboardEvent = event && typeof event === "object" && "clipboardData" in event ? event : null;
|
|
492
|
+
const clipboardData = getCurrentBlockClipboardData();
|
|
493
|
+
if (!clipboardData) return false;
|
|
494
|
+
latestBlockClipboardDataRef.current = clipboardData;
|
|
495
|
+
if (clipboardEvent?.clipboardData) {
|
|
496
|
+
clipboardEvent.preventDefault();
|
|
497
|
+
setBlockClipboardDataTransfer(clipboardEvent.clipboardData, clipboardData);
|
|
498
|
+
return true;
|
|
499
|
+
}
|
|
500
|
+
if (event && typeof event === "object" && "preventDefault" in event && typeof event.preventDefault === "function") event.preventDefault();
|
|
501
|
+
writeBlockClipboardDataToNativeClipboard(editor, clipboardData);
|
|
502
|
+
return true;
|
|
503
|
+
}, COMMAND_PRIORITY_CRITICAL);
|
|
504
|
+
const unregCut = editor.registerCommand(CUT_COMMAND, (event) => {
|
|
505
|
+
const clipboardEvent = event && typeof event === "object" && "clipboardData" in event ? event : null;
|
|
506
|
+
let keysToDelete = [];
|
|
507
|
+
let clipboardData = null;
|
|
508
|
+
editor.getEditorState().read(() => {
|
|
509
|
+
const nodes = getOwnedSelectionNodes();
|
|
510
|
+
if (nodes.length === 0) return;
|
|
511
|
+
clipboardData = buildBlockClipboardData(editor, nodes);
|
|
512
|
+
keysToDelete = nodes.map((node) => node.getKey());
|
|
513
|
+
});
|
|
514
|
+
if (keysToDelete.length === 0 || !clipboardData) return false;
|
|
515
|
+
latestBlockClipboardDataRef.current = clipboardData;
|
|
516
|
+
if (clipboardEvent?.clipboardData) {
|
|
517
|
+
clipboardEvent.preventDefault();
|
|
518
|
+
setBlockClipboardDataTransfer(clipboardEvent.clipboardData, clipboardData);
|
|
519
|
+
} else {
|
|
520
|
+
if (event && typeof event === "object" && "preventDefault" in event && typeof event.preventDefault === "function") event.preventDefault();
|
|
521
|
+
writeBlockClipboardDataToNativeClipboard(editor, clipboardData);
|
|
522
|
+
}
|
|
523
|
+
deleteBlocksByKeys(keysToDelete);
|
|
524
|
+
return true;
|
|
525
|
+
}, COMMAND_PRIORITY_CRITICAL);
|
|
526
|
+
const unregPaste = editor.registerCommand(PASTE_COMMAND, (event) => {
|
|
527
|
+
const clipboardData = getDataTransferFromPasteEvent(event);
|
|
528
|
+
if (!clipboardData) return false;
|
|
529
|
+
const dataTransferOnly = isDataTransferOnlyPasteEvent(event);
|
|
530
|
+
if (!(dataTransferOnly ? hasInsertableClipboardData(clipboardData) : hasPasteableClipboardData(clipboardData))) return false;
|
|
531
|
+
const nodes = getOwnedSelectionNodes();
|
|
532
|
+
if (nodes.length === 0) return false;
|
|
533
|
+
removeTopLevelNodesAndCreatePasteTarget(nodes);
|
|
534
|
+
clearBlockSelectionState();
|
|
535
|
+
if (dataTransferOnly) {
|
|
536
|
+
if (event && typeof event === "object" && "preventDefault" in event && typeof event.preventDefault === "function") event.preventDefault();
|
|
537
|
+
return insertDataTransferForBlockSelectionPaste(editor, clipboardData);
|
|
538
|
+
}
|
|
539
|
+
return false;
|
|
540
|
+
}, COMMAND_PRIORITY_CRITICAL);
|
|
541
|
+
const handleDeleteBlocks = (payload) => {
|
|
542
|
+
let keysToDelete = [];
|
|
543
|
+
editor.getEditorState().read(() => {
|
|
544
|
+
keysToDelete = getOwnedSelectionNodes().map((node) => node.getKey());
|
|
545
|
+
});
|
|
546
|
+
if (keysToDelete.length === 0) return false;
|
|
547
|
+
if (payload && typeof payload !== "boolean" && "preventDefault" in payload && typeof payload.preventDefault === "function") payload.preventDefault();
|
|
548
|
+
deleteBlocksByKeys(keysToDelete);
|
|
549
|
+
return true;
|
|
550
|
+
};
|
|
551
|
+
const unregBackspace = editor.registerCommand(KEY_BACKSPACE_COMMAND, handleDeleteBlocks, COMMAND_PRIORITY_CRITICAL);
|
|
552
|
+
const unregDelete = editor.registerCommand(KEY_DELETE_COMMAND, handleDeleteBlocks, COMMAND_PRIORITY_CRITICAL);
|
|
553
|
+
const unregRemoveText = editor.registerCommand(REMOVE_TEXT_COMMAND, handleDeleteBlocks, COMMAND_PRIORITY_CRITICAL);
|
|
554
|
+
const unregDeleteCharacter = editor.registerCommand(DELETE_CHARACTER_COMMAND, handleDeleteBlocks, COMMAND_PRIORITY_CRITICAL);
|
|
555
|
+
return () => {
|
|
556
|
+
unregKeyDown();
|
|
557
|
+
unregShiftDown();
|
|
558
|
+
unregShiftUp();
|
|
559
|
+
unregSelectAll();
|
|
560
|
+
unregEscape();
|
|
561
|
+
unregCopy();
|
|
562
|
+
unregCut();
|
|
563
|
+
unregPaste();
|
|
564
|
+
unregBackspace();
|
|
565
|
+
unregDelete();
|
|
566
|
+
unregRemoveText();
|
|
567
|
+
unregDeleteCharacter();
|
|
568
|
+
};
|
|
569
|
+
}, [
|
|
570
|
+
clearBlockSelectionState,
|
|
571
|
+
deleteBlocksByKeys,
|
|
572
|
+
editor,
|
|
573
|
+
getCurrentBlockClipboardData,
|
|
574
|
+
getOwnedSelectionNodes,
|
|
575
|
+
replaceBlockSelectionWithDataTransfer
|
|
576
|
+
]);
|
|
577
|
+
const selectBlock = useCallback((nodeKey, shiftKey) => {
|
|
578
|
+
editor.update(() => {
|
|
579
|
+
if (shiftKey && anchorKeyRef.current) {
|
|
580
|
+
focusKeyRef.current = nodeKey;
|
|
581
|
+
$selectBlockRange(anchorKeyRef.current, nodeKey);
|
|
582
|
+
const children = $getRoot().getChildren();
|
|
583
|
+
const anchorIdx = children.findIndex((c) => c.getKey() === anchorKeyRef.current);
|
|
584
|
+
const focusIdx = children.findIndex((c) => c.getKey() === nodeKey);
|
|
585
|
+
if (anchorIdx !== -1 && focusIdx !== -1) {
|
|
586
|
+
const start = Math.min(anchorIdx, focusIdx);
|
|
587
|
+
const end = Math.max(anchorIdx, focusIdx);
|
|
588
|
+
blockSelectionKeysRef.current = new Set(children.slice(start, end + 1).map((c) => c.getKey()));
|
|
589
|
+
}
|
|
590
|
+
} else {
|
|
591
|
+
anchorKeyRef.current = nodeKey;
|
|
592
|
+
focusKeyRef.current = nodeKey;
|
|
593
|
+
blockSelectionKeysRef.current = new Set([nodeKey]);
|
|
594
|
+
const sel = $createNodeSelection();
|
|
595
|
+
sel.add(nodeKey);
|
|
596
|
+
$setSelection(sel);
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
}, [editor]);
|
|
600
|
+
const getSelectedKeys = useCallback(() => {
|
|
601
|
+
if (blockSelectionKeysRef.current.size === 0) return [];
|
|
602
|
+
let keys = [];
|
|
603
|
+
editor.getEditorState().read(() => {
|
|
604
|
+
keys = getTopLevelNodesByKeys(blockSelectionKeysRef.current).map((node) => node.getKey());
|
|
605
|
+
});
|
|
606
|
+
return keys;
|
|
607
|
+
}, [editor, getTopLevelNodesByKeys]);
|
|
608
|
+
const deleteSelectedBlocks = useCallback((fallbackNodeKey) => {
|
|
609
|
+
const selectedKeys = getSelectedKeys();
|
|
610
|
+
const keys = selectedKeys.length > 0 ? selectedKeys : fallbackNodeKey ? [fallbackNodeKey] : [];
|
|
611
|
+
if (keys.length === 0) return;
|
|
612
|
+
deleteBlocksByKeys(keys);
|
|
613
|
+
}, [deleteBlocksByKeys, getSelectedKeys]);
|
|
614
|
+
return {
|
|
615
|
+
selectBlock,
|
|
616
|
+
getSelectedKeys,
|
|
617
|
+
isBlockSelectionActive: useCallback(() => blockSelectionKeysRef.current.size > 0, []),
|
|
618
|
+
deleteSelectedBlocks
|
|
619
|
+
};
|
|
786
620
|
}
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
621
|
+
//#endregion
|
|
622
|
+
//#region src/BlockHandlePlugin.tsx
|
|
623
|
+
var DRAG_DATA_KEY = "application/x-rich-editor-drag";
|
|
624
|
+
var HIDE_DELAY = 300;
|
|
625
|
+
var HANDLE_OFFSET = 52;
|
|
626
|
+
var TURN_INTO_ITEMS = [
|
|
627
|
+
{
|
|
628
|
+
key: "paragraph",
|
|
629
|
+
label: "Text",
|
|
630
|
+
icon: Type
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
key: "h1",
|
|
634
|
+
label: "Heading 1",
|
|
635
|
+
icon: Heading1
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
key: "h2",
|
|
639
|
+
label: "Heading 2",
|
|
640
|
+
icon: Heading2
|
|
641
|
+
},
|
|
642
|
+
{
|
|
643
|
+
key: "h3",
|
|
644
|
+
label: "Heading 3",
|
|
645
|
+
icon: Heading3
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
key: "bullet",
|
|
649
|
+
label: "Bullet List",
|
|
650
|
+
icon: List
|
|
651
|
+
},
|
|
652
|
+
{
|
|
653
|
+
key: "numbered",
|
|
654
|
+
label: "Numbered List",
|
|
655
|
+
icon: ListOrdered
|
|
656
|
+
},
|
|
657
|
+
{
|
|
658
|
+
key: "todo",
|
|
659
|
+
label: "To-do",
|
|
660
|
+
icon: ListChecks
|
|
661
|
+
},
|
|
662
|
+
{
|
|
663
|
+
key: "quote",
|
|
664
|
+
label: "Quote",
|
|
665
|
+
icon: TextQuote
|
|
666
|
+
},
|
|
667
|
+
{
|
|
668
|
+
key: "divider",
|
|
669
|
+
label: "Divider",
|
|
670
|
+
icon: Minus
|
|
671
|
+
},
|
|
672
|
+
{
|
|
673
|
+
key: "code",
|
|
674
|
+
label: "Code",
|
|
675
|
+
icon: Code2
|
|
676
|
+
}
|
|
801
677
|
];
|
|
802
678
|
function getBlockElement(editor, target) {
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
679
|
+
const rootElement = editor.getRootElement();
|
|
680
|
+
if (!rootElement) return null;
|
|
681
|
+
let current = target;
|
|
682
|
+
while (current && current !== rootElement) {
|
|
683
|
+
if (current.parentElement === rootElement) return current;
|
|
684
|
+
current = current.parentElement;
|
|
685
|
+
}
|
|
686
|
+
return null;
|
|
811
687
|
}
|
|
812
688
|
function getNearestBlockByY(rootElement, clientY) {
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
}
|
|
829
|
-
return nearestBlock;
|
|
689
|
+
const blocks = [...rootElement.children].filter((child) => child instanceof HTMLElement);
|
|
690
|
+
if (!blocks.length) return null;
|
|
691
|
+
let nearestBlock = null;
|
|
692
|
+
let nearestDistance = Number.POSITIVE_INFINITY;
|
|
693
|
+
for (const block of blocks) {
|
|
694
|
+
const rect = block.getBoundingClientRect();
|
|
695
|
+
if (rect.height <= 0) continue;
|
|
696
|
+
if (clientY >= rect.top && clientY <= rect.bottom) return block;
|
|
697
|
+
const distance = clientY < rect.top ? rect.top - clientY : clientY - rect.bottom;
|
|
698
|
+
if (distance < nearestDistance) {
|
|
699
|
+
nearestDistance = distance;
|
|
700
|
+
nearestBlock = block;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
return nearestBlock;
|
|
830
704
|
}
|
|
831
705
|
function getDropTargetBlock(editor, rootElement, event) {
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
706
|
+
const rootRect = rootElement.getBoundingClientRect();
|
|
707
|
+
if (rootRect.width <= 0 || rootRect.height <= 0) return null;
|
|
708
|
+
if (event.clientY < rootRect.top || event.clientY > rootRect.bottom) return null;
|
|
709
|
+
const points = [{
|
|
710
|
+
x: event.clientX,
|
|
711
|
+
y: event.clientY
|
|
712
|
+
}];
|
|
713
|
+
const clampedX = Math.min(rootRect.right - 1, Math.max(rootRect.left + 1, event.clientX));
|
|
714
|
+
if (clampedX !== event.clientX) points.unshift({
|
|
715
|
+
x: clampedX,
|
|
716
|
+
y: event.clientY
|
|
717
|
+
});
|
|
718
|
+
for (const point of points) {
|
|
719
|
+
const element = document.elementFromPoint(point.x, point.y);
|
|
720
|
+
if (!(element instanceof HTMLElement)) continue;
|
|
721
|
+
const block = getBlockElement(editor, element);
|
|
722
|
+
if (block) return block;
|
|
723
|
+
}
|
|
724
|
+
const { target } = event;
|
|
725
|
+
if (target instanceof HTMLElement) {
|
|
726
|
+
const block = getBlockElement(editor, target);
|
|
727
|
+
if (block) return block;
|
|
728
|
+
}
|
|
729
|
+
return getNearestBlockByY(rootElement, event.clientY);
|
|
854
730
|
}
|
|
855
731
|
function toPagePosition(rect) {
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
732
|
+
return {
|
|
733
|
+
top: rect.top + window.scrollY,
|
|
734
|
+
left: rect.left + window.scrollX
|
|
735
|
+
};
|
|
860
736
|
}
|
|
861
737
|
function $cloneNode(node) {
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
clone.append($cloneNode(child));
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
return clone;
|
|
738
|
+
const Klass = node.constructor;
|
|
739
|
+
const serialized = node.exportJSON();
|
|
740
|
+
const clone = Klass.importJSON(serialized);
|
|
741
|
+
if ($isElementNode(node) && $isElementNode(clone)) for (const child of node.getChildren()) clone.append($cloneNode(child));
|
|
742
|
+
return clone;
|
|
871
743
|
}
|
|
872
744
|
function BlockHandleInner({ editor }) {
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
)
|
|
1447
|
-
] });
|
|
745
|
+
const { className: portalClassName, theme } = usePortalTheme();
|
|
746
|
+
const [handle, setHandle] = useState({
|
|
747
|
+
visible: false,
|
|
748
|
+
top: 0,
|
|
749
|
+
left: 0,
|
|
750
|
+
nodeKey: null
|
|
751
|
+
});
|
|
752
|
+
const [dropLine, setDropLine] = useState({
|
|
753
|
+
visible: false,
|
|
754
|
+
top: 0,
|
|
755
|
+
left: 0,
|
|
756
|
+
width: 0
|
|
757
|
+
});
|
|
758
|
+
const activeBlockRef = useRef(null);
|
|
759
|
+
const hideTimerRef = useRef(null);
|
|
760
|
+
const hoveringHandleRef = useRef(false);
|
|
761
|
+
const menuOpenCountRef = useRef(0);
|
|
762
|
+
const dragPreviewRef = useRef(null);
|
|
763
|
+
const draggingBlockRef = useRef(null);
|
|
764
|
+
const draggingBlockKeysRef = useRef(null);
|
|
765
|
+
const { selectBlock, getSelectedKeys, deleteSelectedBlocks } = useBlockSelection(editor);
|
|
766
|
+
const clearHideTimer = useCallback(() => {
|
|
767
|
+
if (hideTimerRef.current) {
|
|
768
|
+
clearTimeout(hideTimerRef.current);
|
|
769
|
+
hideTimerRef.current = null;
|
|
770
|
+
}
|
|
771
|
+
}, []);
|
|
772
|
+
const scheduleHide = useCallback(() => {
|
|
773
|
+
clearHideTimer();
|
|
774
|
+
hideTimerRef.current = setTimeout(() => {
|
|
775
|
+
if (!hoveringHandleRef.current && menuOpenCountRef.current === 0) {
|
|
776
|
+
activeBlockRef.current = null;
|
|
777
|
+
setHandle((state) => ({
|
|
778
|
+
...state,
|
|
779
|
+
visible: false,
|
|
780
|
+
nodeKey: null
|
|
781
|
+
}));
|
|
782
|
+
}
|
|
783
|
+
}, HIDE_DELAY);
|
|
784
|
+
}, [clearHideTimer]);
|
|
785
|
+
const onHandleEnter = useCallback(() => {
|
|
786
|
+
hoveringHandleRef.current = true;
|
|
787
|
+
clearHideTimer();
|
|
788
|
+
}, [clearHideTimer]);
|
|
789
|
+
const onHandleLeave = useCallback(() => {
|
|
790
|
+
hoveringHandleRef.current = false;
|
|
791
|
+
scheduleHide();
|
|
792
|
+
}, [scheduleHide]);
|
|
793
|
+
const onMenuOpenChange = useCallback((open) => {
|
|
794
|
+
menuOpenCountRef.current += open ? 1 : -1;
|
|
795
|
+
if (!open) scheduleHide();
|
|
796
|
+
else clearHideTimer();
|
|
797
|
+
}, [clearHideTimer, scheduleHide]);
|
|
798
|
+
const updatePositionFromBlock = useCallback((block) => {
|
|
799
|
+
if (block !== void 0) activeBlockRef.current = block;
|
|
800
|
+
const element = activeBlockRef.current;
|
|
801
|
+
if (!element || !element.isConnected) {
|
|
802
|
+
activeBlockRef.current = null;
|
|
803
|
+
setHandle((state) => state.visible ? {
|
|
804
|
+
...state,
|
|
805
|
+
visible: false,
|
|
806
|
+
nodeKey: null
|
|
807
|
+
} : state);
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
const rootElement = editor.getRootElement();
|
|
811
|
+
if (!rootElement) return;
|
|
812
|
+
const blockRect = element.getBoundingClientRect();
|
|
813
|
+
const rootRect = rootElement.getBoundingClientRect();
|
|
814
|
+
const page = toPagePosition(blockRect);
|
|
815
|
+
let nodeKey = null;
|
|
816
|
+
editor.read(() => {
|
|
817
|
+
const node = $getNearestNodeFromDOMNode(element);
|
|
818
|
+
if (node) nodeKey = node.getKey();
|
|
819
|
+
});
|
|
820
|
+
setHandle({
|
|
821
|
+
visible: true,
|
|
822
|
+
top: page.top,
|
|
823
|
+
left: toPagePosition(rootRect).left - HANDLE_OFFSET,
|
|
824
|
+
nodeKey
|
|
825
|
+
});
|
|
826
|
+
}, [editor]);
|
|
827
|
+
useEffect(() => {
|
|
828
|
+
const rootElement = editor.getRootElement();
|
|
829
|
+
if (!rootElement) return;
|
|
830
|
+
let rafId = null;
|
|
831
|
+
const onMouseMove = (event) => {
|
|
832
|
+
if (rafId !== null) return;
|
|
833
|
+
rafId = requestAnimationFrame(() => {
|
|
834
|
+
rafId = null;
|
|
835
|
+
const target = event.target;
|
|
836
|
+
const block = getBlockElement(editor, target);
|
|
837
|
+
if (block) {
|
|
838
|
+
clearHideTimer();
|
|
839
|
+
updatePositionFromBlock(block);
|
|
840
|
+
}
|
|
841
|
+
});
|
|
842
|
+
};
|
|
843
|
+
const onMouseLeave = () => {
|
|
844
|
+
if (!hoveringHandleRef.current && menuOpenCountRef.current === 0) scheduleHide();
|
|
845
|
+
};
|
|
846
|
+
rootElement.addEventListener("mousemove", onMouseMove);
|
|
847
|
+
rootElement.addEventListener("mouseleave", onMouseLeave);
|
|
848
|
+
return () => {
|
|
849
|
+
if (rafId !== null) cancelAnimationFrame(rafId);
|
|
850
|
+
rootElement.removeEventListener("mousemove", onMouseMove);
|
|
851
|
+
rootElement.removeEventListener("mouseleave", onMouseLeave);
|
|
852
|
+
clearHideTimer();
|
|
853
|
+
};
|
|
854
|
+
}, [
|
|
855
|
+
clearHideTimer,
|
|
856
|
+
editor,
|
|
857
|
+
scheduleHide,
|
|
858
|
+
updatePositionFromBlock
|
|
859
|
+
]);
|
|
860
|
+
useEffect(() => {
|
|
861
|
+
const update = () => updatePositionFromBlock();
|
|
862
|
+
window.addEventListener("scroll", update, true);
|
|
863
|
+
window.addEventListener("resize", update);
|
|
864
|
+
return () => {
|
|
865
|
+
window.removeEventListener("scroll", update, true);
|
|
866
|
+
window.removeEventListener("resize", update);
|
|
867
|
+
};
|
|
868
|
+
}, [updatePositionFromBlock]);
|
|
869
|
+
useEffect(() => editor.registerUpdateListener(() => updatePositionFromBlock()), [editor, updatePositionFromBlock]);
|
|
870
|
+
const handleAddBlock = useCallback(() => {
|
|
871
|
+
if (!handle.nodeKey) return;
|
|
872
|
+
editor.update(() => {
|
|
873
|
+
const node = $getNodeByKey(handle.nodeKey);
|
|
874
|
+
if (!node) return;
|
|
875
|
+
const paragraph = $createParagraphNode();
|
|
876
|
+
node.insertAfter(paragraph);
|
|
877
|
+
paragraph.selectStart();
|
|
878
|
+
});
|
|
879
|
+
}, [editor, handle.nodeKey]);
|
|
880
|
+
const handleTurnInto = useCallback((type) => {
|
|
881
|
+
const { nodeKey } = handle;
|
|
882
|
+
if (!nodeKey) return;
|
|
883
|
+
if ([
|
|
884
|
+
"bullet",
|
|
885
|
+
"numbered",
|
|
886
|
+
"todo",
|
|
887
|
+
"divider"
|
|
888
|
+
].includes(type)) {
|
|
889
|
+
editor.update(() => {
|
|
890
|
+
const node = $getNodeByKey(nodeKey);
|
|
891
|
+
if (!node) return;
|
|
892
|
+
if ($isElementNode(node)) node.selectStart();
|
|
893
|
+
});
|
|
894
|
+
const commands = {
|
|
895
|
+
bullet: INSERT_UNORDERED_LIST_COMMAND,
|
|
896
|
+
numbered: INSERT_ORDERED_LIST_COMMAND,
|
|
897
|
+
todo: INSERT_CHECK_LIST_COMMAND,
|
|
898
|
+
divider: INSERT_HORIZONTAL_RULE_COMMAND
|
|
899
|
+
};
|
|
900
|
+
editor.dispatchCommand(commands[type], void 0);
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
editor.update(() => {
|
|
904
|
+
const node = $getNodeByKey(nodeKey);
|
|
905
|
+
if (!node || !$isElementNode(node)) return;
|
|
906
|
+
node.selectStart();
|
|
907
|
+
const selection = $getSelection();
|
|
908
|
+
if (!$isRangeSelection(selection)) return;
|
|
909
|
+
const create = {
|
|
910
|
+
paragraph: () => $createParagraphNode(),
|
|
911
|
+
h1: () => $createHeadingNode("h1"),
|
|
912
|
+
h2: () => $createHeadingNode("h2"),
|
|
913
|
+
h3: () => $createHeadingNode("h3"),
|
|
914
|
+
quote: () => $createQuoteNode(),
|
|
915
|
+
code: () => $createCodeNode()
|
|
916
|
+
}[type];
|
|
917
|
+
if (create) $setBlocksType(selection, create);
|
|
918
|
+
});
|
|
919
|
+
}, [editor, handle]);
|
|
920
|
+
const handleDelete = useCallback(() => {
|
|
921
|
+
deleteSelectedBlocks(handle.nodeKey);
|
|
922
|
+
setHandle((state) => ({
|
|
923
|
+
...state,
|
|
924
|
+
visible: false,
|
|
925
|
+
nodeKey: null
|
|
926
|
+
}));
|
|
927
|
+
}, [deleteSelectedBlocks, handle.nodeKey]);
|
|
928
|
+
const handleDuplicate = useCallback(() => {
|
|
929
|
+
const selectedKeys = getSelectedKeys();
|
|
930
|
+
const keys = selectedKeys.length > 0 ? selectedKeys : handle.nodeKey ? [handle.nodeKey] : [];
|
|
931
|
+
if (!keys.length) return;
|
|
932
|
+
editor.update(() => {
|
|
933
|
+
const children = $getRoot().getChildren();
|
|
934
|
+
const keySet = new Set(keys);
|
|
935
|
+
const nodesToDuplicate = children.filter((child) => keySet.has(child.getKey()));
|
|
936
|
+
if (!nodesToDuplicate.length) return;
|
|
937
|
+
let insertAfter = nodesToDuplicate.at(-1);
|
|
938
|
+
for (const node of nodesToDuplicate) {
|
|
939
|
+
const clone = $cloneNode(node);
|
|
940
|
+
insertAfter.insertAfter(clone);
|
|
941
|
+
insertAfter = clone;
|
|
942
|
+
}
|
|
943
|
+
});
|
|
944
|
+
}, [
|
|
945
|
+
editor,
|
|
946
|
+
getSelectedKeys,
|
|
947
|
+
handle.nodeKey
|
|
948
|
+
]);
|
|
949
|
+
const handleMoveUp = useCallback(() => {
|
|
950
|
+
const selectedKeys = getSelectedKeys();
|
|
951
|
+
const keys = selectedKeys.length > 0 ? selectedKeys : handle.nodeKey ? [handle.nodeKey] : [];
|
|
952
|
+
if (!keys.length) return;
|
|
953
|
+
editor.update(() => {
|
|
954
|
+
const children = $getRoot().getChildren();
|
|
955
|
+
const keySet = new Set(keys);
|
|
956
|
+
const selectedNodes = children.filter((child) => keySet.has(child.getKey()));
|
|
957
|
+
if (!selectedNodes.length) return;
|
|
958
|
+
const previousSibling = selectedNodes[0].getPreviousSibling();
|
|
959
|
+
if (!previousSibling || keySet.has(previousSibling.getKey())) return;
|
|
960
|
+
const lastSelected = selectedNodes.at(-1);
|
|
961
|
+
previousSibling.remove();
|
|
962
|
+
lastSelected.insertAfter(previousSibling);
|
|
963
|
+
});
|
|
964
|
+
}, [
|
|
965
|
+
editor,
|
|
966
|
+
getSelectedKeys,
|
|
967
|
+
handle.nodeKey
|
|
968
|
+
]);
|
|
969
|
+
const handleMoveDown = useCallback(() => {
|
|
970
|
+
const selectedKeys = getSelectedKeys();
|
|
971
|
+
const keys = selectedKeys.length > 0 ? selectedKeys : handle.nodeKey ? [handle.nodeKey] : [];
|
|
972
|
+
if (!keys.length) return;
|
|
973
|
+
editor.update(() => {
|
|
974
|
+
const children = $getRoot().getChildren();
|
|
975
|
+
const keySet = new Set(keys);
|
|
976
|
+
const selectedNodes = children.filter((child) => keySet.has(child.getKey()));
|
|
977
|
+
if (!selectedNodes.length) return;
|
|
978
|
+
const nextSibling = selectedNodes.at(-1).getNextSibling();
|
|
979
|
+
if (!nextSibling || keySet.has(nextSibling.getKey())) return;
|
|
980
|
+
const firstSelected = selectedNodes[0];
|
|
981
|
+
nextSibling.remove();
|
|
982
|
+
firstSelected.insertBefore(nextSibling);
|
|
983
|
+
});
|
|
984
|
+
}, [
|
|
985
|
+
editor,
|
|
986
|
+
getSelectedKeys,
|
|
987
|
+
handle.nodeKey
|
|
988
|
+
]);
|
|
989
|
+
const [gripMenuOpen, setGripMenuOpen] = useState(false);
|
|
990
|
+
const dragStartedRef = useRef(false);
|
|
991
|
+
const clearDragVisualState = useCallback(() => {
|
|
992
|
+
const preview = dragPreviewRef.current;
|
|
993
|
+
if (preview) {
|
|
994
|
+
preview.remove();
|
|
995
|
+
dragPreviewRef.current = null;
|
|
996
|
+
}
|
|
997
|
+
const draggingBlock$1 = draggingBlockRef.current;
|
|
998
|
+
if (draggingBlock$1) {
|
|
999
|
+
draggingBlock$1.classList.remove(draggingBlock);
|
|
1000
|
+
draggingBlockRef.current = null;
|
|
1001
|
+
}
|
|
1002
|
+
const draggingKeys = draggingBlockKeysRef.current;
|
|
1003
|
+
if (draggingKeys) {
|
|
1004
|
+
for (const key of draggingKeys) editor.getElementByKey(key)?.classList.remove(draggingBlock);
|
|
1005
|
+
draggingBlockKeysRef.current = null;
|
|
1006
|
+
}
|
|
1007
|
+
}, [editor]);
|
|
1008
|
+
const onGripDragStart = useCallback((event) => {
|
|
1009
|
+
dragStartedRef.current = true;
|
|
1010
|
+
if (!event.dataTransfer || !handle.nodeKey) return;
|
|
1011
|
+
const selectedKeys = getSelectedKeys();
|
|
1012
|
+
const dragKeys = selectedKeys.length > 0 && selectedKeys.includes(handle.nodeKey) ? selectedKeys : [handle.nodeKey];
|
|
1013
|
+
event.dataTransfer.setData(DRAG_DATA_KEY, JSON.stringify(dragKeys));
|
|
1014
|
+
event.dataTransfer.effectAllowed = "move";
|
|
1015
|
+
const block = activeBlockRef.current;
|
|
1016
|
+
if (!block) return;
|
|
1017
|
+
clearDragVisualState();
|
|
1018
|
+
const rect = block.getBoundingClientRect();
|
|
1019
|
+
const preview = block.cloneNode(true);
|
|
1020
|
+
preview.classList.add(dragPreview);
|
|
1021
|
+
preview.style.width = `${rect.width}px`;
|
|
1022
|
+
preview.style.position = "relative";
|
|
1023
|
+
if (dragKeys.length > 1) {
|
|
1024
|
+
const badge = document.createElement("div");
|
|
1025
|
+
badge.className = dragCountBadge;
|
|
1026
|
+
badge.textContent = String(dragKeys.length);
|
|
1027
|
+
preview.appendChild(badge);
|
|
1028
|
+
}
|
|
1029
|
+
if (portalClassName) {
|
|
1030
|
+
const wrapper = document.createElement("div");
|
|
1031
|
+
wrapper.className = portalClassName;
|
|
1032
|
+
wrapper.setAttribute("data-theme", theme);
|
|
1033
|
+
wrapper.style.cssText = "position:fixed;top:-10000px;left:-10000px;pointer-events:none";
|
|
1034
|
+
wrapper.appendChild(preview);
|
|
1035
|
+
document.body.append(wrapper);
|
|
1036
|
+
dragPreviewRef.current = wrapper;
|
|
1037
|
+
} else {
|
|
1038
|
+
document.body.append(preview);
|
|
1039
|
+
dragPreviewRef.current = preview;
|
|
1040
|
+
}
|
|
1041
|
+
if (dragKeys.length > 1) {
|
|
1042
|
+
for (const key of dragKeys) editor.getElementByKey(key)?.classList.add(draggingBlock);
|
|
1043
|
+
draggingBlockKeysRef.current = dragKeys;
|
|
1044
|
+
} else {
|
|
1045
|
+
draggingBlockRef.current = block;
|
|
1046
|
+
block.classList.add(draggingBlock);
|
|
1047
|
+
}
|
|
1048
|
+
const offsetX = Math.max(12, Math.min(rect.width - 12, event.clientX - rect.left));
|
|
1049
|
+
const offsetY = Math.max(8, Math.min(rect.height - 8, event.clientY - rect.top));
|
|
1050
|
+
event.dataTransfer.setDragImage(preview, offsetX, offsetY);
|
|
1051
|
+
}, [
|
|
1052
|
+
clearDragVisualState,
|
|
1053
|
+
editor,
|
|
1054
|
+
getSelectedKeys,
|
|
1055
|
+
handle.nodeKey,
|
|
1056
|
+
portalClassName,
|
|
1057
|
+
theme
|
|
1058
|
+
]);
|
|
1059
|
+
const onGripOpenChange = useCallback((open) => {
|
|
1060
|
+
setGripMenuOpen((previous) => {
|
|
1061
|
+
if (previous === open) return previous;
|
|
1062
|
+
onMenuOpenChange(open);
|
|
1063
|
+
return open;
|
|
1064
|
+
});
|
|
1065
|
+
}, [onMenuOpenChange]);
|
|
1066
|
+
const onGripMouseDownCapture = useCallback((event) => {
|
|
1067
|
+
dragStartedRef.current = false;
|
|
1068
|
+
if (event.button === 0) event.stopPropagation();
|
|
1069
|
+
}, []);
|
|
1070
|
+
const onGripClick = useCallback((event) => {
|
|
1071
|
+
if (event.detail === 0) return;
|
|
1072
|
+
event.preventDefault();
|
|
1073
|
+
event.stopPropagation();
|
|
1074
|
+
if (dragStartedRef.current) {
|
|
1075
|
+
dragStartedRef.current = false;
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
if (!handle.nodeKey) return;
|
|
1079
|
+
const { nodeKey } = handle;
|
|
1080
|
+
const { shiftKey } = event;
|
|
1081
|
+
editor.focus(() => selectBlock(nodeKey, shiftKey));
|
|
1082
|
+
}, [
|
|
1083
|
+
editor,
|
|
1084
|
+
handle.nodeKey,
|
|
1085
|
+
selectBlock
|
|
1086
|
+
]);
|
|
1087
|
+
const onGripContextMenu = useCallback((event) => {
|
|
1088
|
+
event.preventDefault();
|
|
1089
|
+
if (handle.nodeKey) {
|
|
1090
|
+
if (!getSelectedKeys().includes(handle.nodeKey)) selectBlock(handle.nodeKey, false);
|
|
1091
|
+
}
|
|
1092
|
+
onGripOpenChange(true);
|
|
1093
|
+
}, [
|
|
1094
|
+
getSelectedKeys,
|
|
1095
|
+
handle.nodeKey,
|
|
1096
|
+
onGripOpenChange,
|
|
1097
|
+
selectBlock
|
|
1098
|
+
]);
|
|
1099
|
+
useEffect(() => {
|
|
1100
|
+
const rootElement = editor.getRootElement();
|
|
1101
|
+
if (!rootElement) return;
|
|
1102
|
+
const unregDragOver = editor.registerCommand(DRAGOVER_COMMAND, (event) => {
|
|
1103
|
+
if (!event.dataTransfer?.types.includes(DRAG_DATA_KEY)) return false;
|
|
1104
|
+
event.preventDefault();
|
|
1105
|
+
event.dataTransfer.dropEffect = "move";
|
|
1106
|
+
const block = getDropTargetBlock(editor, rootElement, event);
|
|
1107
|
+
if (!block) {
|
|
1108
|
+
setDropLine((state) => state.visible ? {
|
|
1109
|
+
...state,
|
|
1110
|
+
visible: false
|
|
1111
|
+
} : state);
|
|
1112
|
+
return true;
|
|
1113
|
+
}
|
|
1114
|
+
const rect = block.getBoundingClientRect();
|
|
1115
|
+
const rootRect = rootElement.getBoundingClientRect();
|
|
1116
|
+
const midY = rect.top + rect.height / 2;
|
|
1117
|
+
setDropLine({
|
|
1118
|
+
visible: true,
|
|
1119
|
+
top: (event.clientY < midY ? rect.top : rect.bottom) + window.scrollY,
|
|
1120
|
+
left: rootRect.left + window.scrollX,
|
|
1121
|
+
width: rootRect.width
|
|
1122
|
+
});
|
|
1123
|
+
return true;
|
|
1124
|
+
}, COMMAND_PRIORITY_HIGH);
|
|
1125
|
+
const unregDrop = editor.registerCommand(DROP_COMMAND, (event) => {
|
|
1126
|
+
const raw = event.dataTransfer?.getData(DRAG_DATA_KEY);
|
|
1127
|
+
if (!raw) return false;
|
|
1128
|
+
event.preventDefault();
|
|
1129
|
+
setDropLine((state) => ({
|
|
1130
|
+
...state,
|
|
1131
|
+
visible: false
|
|
1132
|
+
}));
|
|
1133
|
+
clearDragVisualState();
|
|
1134
|
+
let draggedKeys;
|
|
1135
|
+
try {
|
|
1136
|
+
const parsed = JSON.parse(raw);
|
|
1137
|
+
draggedKeys = Array.isArray(parsed) && parsed.every((key) => typeof key === "string") ? parsed : [raw];
|
|
1138
|
+
} catch {
|
|
1139
|
+
draggedKeys = [raw];
|
|
1140
|
+
}
|
|
1141
|
+
if (!draggedKeys.length) return false;
|
|
1142
|
+
const block = getDropTargetBlock(editor, rootElement, event);
|
|
1143
|
+
if (!block) return false;
|
|
1144
|
+
editor.update(() => {
|
|
1145
|
+
const targetNode = $getNearestNodeFromDOMNode(block);
|
|
1146
|
+
if (!targetNode) return;
|
|
1147
|
+
if (draggedKeys.includes(targetNode.getKey())) return;
|
|
1148
|
+
const rect = block.getBoundingClientRect();
|
|
1149
|
+
const insertBefore = event.clientY < rect.top + rect.height / 2;
|
|
1150
|
+
const children = $getRoot().getChildren();
|
|
1151
|
+
const keySet = new Set(draggedKeys);
|
|
1152
|
+
const draggedNodes = children.filter((child) => keySet.has(child.getKey()));
|
|
1153
|
+
for (const node of draggedNodes) node.remove();
|
|
1154
|
+
const freshTarget = $getNodeByKey(targetNode.getKey());
|
|
1155
|
+
if (!freshTarget) return;
|
|
1156
|
+
if (insertBefore) for (let i = draggedNodes.length - 1; i >= 0; i--) freshTarget.insertBefore(draggedNodes[i]);
|
|
1157
|
+
else {
|
|
1158
|
+
let cursor = freshTarget;
|
|
1159
|
+
for (const node of draggedNodes) {
|
|
1160
|
+
cursor.insertAfter(node);
|
|
1161
|
+
cursor = node;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
});
|
|
1165
|
+
return true;
|
|
1166
|
+
}, COMMAND_PRIORITY_HIGH);
|
|
1167
|
+
const unregDragStart = editor.registerCommand(DRAGSTART_COMMAND, (event) => {
|
|
1168
|
+
if (!event.dataTransfer?.types.includes(DRAG_DATA_KEY)) return false;
|
|
1169
|
+
return true;
|
|
1170
|
+
}, COMMAND_PRIORITY_HIGH);
|
|
1171
|
+
const clearDropLine = () => {
|
|
1172
|
+
setDropLine((state) => state.visible ? {
|
|
1173
|
+
...state,
|
|
1174
|
+
visible: false
|
|
1175
|
+
} : state);
|
|
1176
|
+
};
|
|
1177
|
+
const clearDragState = () => {
|
|
1178
|
+
clearDropLine();
|
|
1179
|
+
clearDragVisualState();
|
|
1180
|
+
};
|
|
1181
|
+
const onDragLeave = (event) => {
|
|
1182
|
+
if (event.relatedTarget === null || !rootElement.contains(event.relatedTarget)) clearDropLine();
|
|
1183
|
+
};
|
|
1184
|
+
window.addEventListener("dragend", clearDragState);
|
|
1185
|
+
window.addEventListener("drop", clearDragState);
|
|
1186
|
+
rootElement.addEventListener("dragleave", onDragLeave);
|
|
1187
|
+
return () => {
|
|
1188
|
+
unregDragOver();
|
|
1189
|
+
unregDrop();
|
|
1190
|
+
unregDragStart();
|
|
1191
|
+
window.removeEventListener("dragend", clearDragState);
|
|
1192
|
+
window.removeEventListener("drop", clearDragState);
|
|
1193
|
+
rootElement.removeEventListener("dragleave", onDragLeave);
|
|
1194
|
+
clearDragState();
|
|
1195
|
+
};
|
|
1196
|
+
}, [clearDragVisualState, editor]);
|
|
1197
|
+
useEffect(() => {
|
|
1198
|
+
const rootElement = editor.getRootElement();
|
|
1199
|
+
if (!rootElement) return;
|
|
1200
|
+
const outerContainer = rootElement.closest(".rich-editor");
|
|
1201
|
+
if (!outerContainer) return;
|
|
1202
|
+
let isDragging = false;
|
|
1203
|
+
let lastKey = null;
|
|
1204
|
+
const getBlockKeyAtY = (clientY) => {
|
|
1205
|
+
const block = getNearestBlockByY(rootElement, clientY);
|
|
1206
|
+
if (!block) return null;
|
|
1207
|
+
let nodeKey = null;
|
|
1208
|
+
editor.read(() => {
|
|
1209
|
+
nodeKey = $getNearestNodeFromDOMNode(block)?.getKey() ?? null;
|
|
1210
|
+
});
|
|
1211
|
+
return nodeKey;
|
|
1212
|
+
};
|
|
1213
|
+
const onMouseDown = (event) => {
|
|
1214
|
+
if (event.button !== 0) return;
|
|
1215
|
+
const target = event.target;
|
|
1216
|
+
if (rootElement.contains(target)) return;
|
|
1217
|
+
const nodeKey = getBlockKeyAtY(event.clientY);
|
|
1218
|
+
if (!nodeKey) return;
|
|
1219
|
+
event.preventDefault();
|
|
1220
|
+
editor.focus(() => selectBlock(nodeKey, event.shiftKey));
|
|
1221
|
+
isDragging = true;
|
|
1222
|
+
lastKey = nodeKey;
|
|
1223
|
+
};
|
|
1224
|
+
const onMouseMove = (event) => {
|
|
1225
|
+
if (!isDragging) return;
|
|
1226
|
+
const nodeKey = getBlockKeyAtY(event.clientY);
|
|
1227
|
+
if (!nodeKey || nodeKey === lastKey) return;
|
|
1228
|
+
selectBlock(nodeKey, true);
|
|
1229
|
+
lastKey = nodeKey;
|
|
1230
|
+
};
|
|
1231
|
+
const onMouseUp = () => {
|
|
1232
|
+
isDragging = false;
|
|
1233
|
+
lastKey = null;
|
|
1234
|
+
};
|
|
1235
|
+
outerContainer.addEventListener("mousedown", onMouseDown);
|
|
1236
|
+
window.addEventListener("mousemove", onMouseMove);
|
|
1237
|
+
window.addEventListener("mouseup", onMouseUp);
|
|
1238
|
+
return () => {
|
|
1239
|
+
outerContainer.removeEventListener("mousedown", onMouseDown);
|
|
1240
|
+
window.removeEventListener("mousemove", onMouseMove);
|
|
1241
|
+
window.removeEventListener("mouseup", onMouseUp);
|
|
1242
|
+
};
|
|
1243
|
+
}, [editor, selectBlock]);
|
|
1244
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1245
|
+
...portalClassName ? {
|
|
1246
|
+
"className": portalClassName,
|
|
1247
|
+
"data-theme": theme,
|
|
1248
|
+
"style": { display: "contents" }
|
|
1249
|
+
} : {},
|
|
1250
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1251
|
+
className: `${handleContainer} ${handle.visible ? handleContainerVisible : ""}`,
|
|
1252
|
+
style: {
|
|
1253
|
+
top: handle.top,
|
|
1254
|
+
left: handle.left
|
|
1255
|
+
},
|
|
1256
|
+
onMouseEnter: onHandleEnter,
|
|
1257
|
+
onMouseLeave: onHandleLeave,
|
|
1258
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
1259
|
+
"aria-label": "Add block",
|
|
1260
|
+
className: handleBtn,
|
|
1261
|
+
onClick: handleAddBlock,
|
|
1262
|
+
children: /* @__PURE__ */ jsx(Plus, { size: 14 })
|
|
1263
|
+
}), /* @__PURE__ */ jsxs(DropdownMenu, {
|
|
1264
|
+
open: gripMenuOpen,
|
|
1265
|
+
onOpenChange: onGripOpenChange,
|
|
1266
|
+
children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
|
|
1267
|
+
draggable: true,
|
|
1268
|
+
"aria-label": "Block actions",
|
|
1269
|
+
className: handleBtn,
|
|
1270
|
+
onClick: onGripClick,
|
|
1271
|
+
onContextMenu: onGripContextMenu,
|
|
1272
|
+
onDragStart: onGripDragStart,
|
|
1273
|
+
onMouseDownCapture: onGripMouseDownCapture,
|
|
1274
|
+
children: /* @__PURE__ */ jsx(GripVertical, { size: 14 })
|
|
1275
|
+
}), /* @__PURE__ */ jsxs(DropdownMenuContent, {
|
|
1276
|
+
align: "start",
|
|
1277
|
+
side: "bottom",
|
|
1278
|
+
sideOffset: 4,
|
|
1279
|
+
children: [
|
|
1280
|
+
/* @__PURE__ */ jsxs(DropdownMenuGroup, { children: [/* @__PURE__ */ jsx(DropdownMenuLabel, { children: "TURN INTO" }), TURN_INTO_ITEMS.map((item) => /* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
1281
|
+
onClick: () => handleTurnInto(item.key),
|
|
1282
|
+
children: [/* @__PURE__ */ jsx(item.icon, {}), item.label]
|
|
1283
|
+
}, item.key))] }),
|
|
1284
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
1285
|
+
/* @__PURE__ */ jsxs(DropdownMenuGroup, { children: [
|
|
1286
|
+
/* @__PURE__ */ jsx(DropdownMenuLabel, { children: "ACTIONS" }),
|
|
1287
|
+
/* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
1288
|
+
onClick: handleDuplicate,
|
|
1289
|
+
children: [/* @__PURE__ */ jsx(Copy, {}), "Duplicate"]
|
|
1290
|
+
}),
|
|
1291
|
+
/* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
1292
|
+
onClick: handleMoveUp,
|
|
1293
|
+
children: [/* @__PURE__ */ jsx(ArrowUp, {}), "Move Up"]
|
|
1294
|
+
}),
|
|
1295
|
+
/* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
1296
|
+
onClick: handleMoveDown,
|
|
1297
|
+
children: [/* @__PURE__ */ jsx(ArrowDown, {}), "Move Down"]
|
|
1298
|
+
})
|
|
1299
|
+
] }),
|
|
1300
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
1301
|
+
/* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
1302
|
+
className: menuItemDestructive,
|
|
1303
|
+
onClick: handleDelete,
|
|
1304
|
+
children: [/* @__PURE__ */ jsx(Trash2, {}), "Delete"]
|
|
1305
|
+
})
|
|
1306
|
+
]
|
|
1307
|
+
})]
|
|
1308
|
+
})]
|
|
1309
|
+
}), dropLine.visible && /* @__PURE__ */ jsx("div", {
|
|
1310
|
+
className: "iihqkc5",
|
|
1311
|
+
style: {
|
|
1312
|
+
top: dropLine.top,
|
|
1313
|
+
left: dropLine.left,
|
|
1314
|
+
width: dropLine.width
|
|
1315
|
+
}
|
|
1316
|
+
})]
|
|
1317
|
+
});
|
|
1448
1318
|
}
|
|
1449
1319
|
function BlockHandlePlugin() {
|
|
1450
|
-
|
|
1451
|
-
|
|
1320
|
+
const [editor] = useLexicalComposerContext();
|
|
1321
|
+
return createPortal(/* @__PURE__ */ jsx(BlockHandleInner, { editor }), document.body);
|
|
1452
1322
|
}
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
};
|
|
1323
|
+
//#endregion
|
|
1324
|
+
export { BlockHandlePlugin };
|