onchain-lexical-components 0.0.1 → 0.0.3
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/DraggableBlockPlugin.js +805 -0
- package/dist/DraggableBlockPlugin.mjs +803 -0
- package/dist/MarkdownShortcutPlugin.js +29 -0
- package/dist/MarkdownShortcutPlugin.mjs +27 -0
- package/package.json +8 -4
- package/src/DraggableBlockPlugin/draggableBlockPlugin.tsx +5 -3
- package/src/DraggableBlockPlugin/index.module.less +2 -2
|
@@ -0,0 +1,805 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
var LexicalComposerContext = require('@lexical/react/LexicalComposerContext');
|
|
12
|
+
var lexical = require('lexical');
|
|
13
|
+
var onchainLexicalInstance = require('onchain-lexical-instance');
|
|
14
|
+
var react = require('react');
|
|
15
|
+
var richText = require('@lexical/rich-text');
|
|
16
|
+
var utils = require('@lexical/utils');
|
|
17
|
+
var traversal = require('onchain-utility/traversal');
|
|
18
|
+
var reactDom = require('react-dom');
|
|
19
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
23
|
+
*
|
|
24
|
+
* This source code is licensed under the MIT license found in the
|
|
25
|
+
* LICENSE file in the root directory of this source tree.
|
|
26
|
+
*
|
|
27
|
+
*/
|
|
28
|
+
const useLatest = value => {
|
|
29
|
+
const latest = react.useRef(value);
|
|
30
|
+
react.useEffect(() => {
|
|
31
|
+
latest.current = value;
|
|
32
|
+
}, [value]);
|
|
33
|
+
return latest;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
38
|
+
*
|
|
39
|
+
* This source code is licensed under the MIT license found in the
|
|
40
|
+
* LICENSE file in the root directory of this source tree.
|
|
41
|
+
*
|
|
42
|
+
*/
|
|
43
|
+
class Point {
|
|
44
|
+
constructor(x, y) {
|
|
45
|
+
this._x = x;
|
|
46
|
+
this._y = y;
|
|
47
|
+
}
|
|
48
|
+
get x() {
|
|
49
|
+
return this._x;
|
|
50
|
+
}
|
|
51
|
+
get y() {
|
|
52
|
+
return this._y;
|
|
53
|
+
}
|
|
54
|
+
equals({
|
|
55
|
+
x,
|
|
56
|
+
y
|
|
57
|
+
}) {
|
|
58
|
+
return this.x === x && this.y === y;
|
|
59
|
+
}
|
|
60
|
+
calcDeltaXTo({
|
|
61
|
+
x
|
|
62
|
+
}) {
|
|
63
|
+
return this.x - x;
|
|
64
|
+
}
|
|
65
|
+
calcDeltaYTo({
|
|
66
|
+
y
|
|
67
|
+
}) {
|
|
68
|
+
return this.y - y;
|
|
69
|
+
}
|
|
70
|
+
calcHorizontalDistanceTo(point) {
|
|
71
|
+
return Math.abs(this.calcDeltaXTo(point));
|
|
72
|
+
}
|
|
73
|
+
calcVerticalDistance(point) {
|
|
74
|
+
return Math.abs(this.calcDeltaYTo(point));
|
|
75
|
+
}
|
|
76
|
+
calcDistanceTo(point) {
|
|
77
|
+
return Math.sqrt(Math.pow(this.calcDeltaXTo(point), 2) + Math.pow(this.calcDeltaYTo(point), 2));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function isPoint(x) {
|
|
81
|
+
return x instanceof Point;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
86
|
+
*
|
|
87
|
+
* This source code is licensed under the MIT license found in the
|
|
88
|
+
* LICENSE file in the root directory of this source tree.
|
|
89
|
+
*
|
|
90
|
+
*/
|
|
91
|
+
class Rectangle {
|
|
92
|
+
constructor(left, top, right, bottom) {
|
|
93
|
+
const [physicTop, physicBottom] = top <= bottom ? [top, bottom] : [bottom, top];
|
|
94
|
+
const [physicLeft, physicRight] = left <= right ? [left, right] : [right, left];
|
|
95
|
+
this._top = physicTop;
|
|
96
|
+
this._right = physicRight;
|
|
97
|
+
this._left = physicLeft;
|
|
98
|
+
this._bottom = physicBottom;
|
|
99
|
+
}
|
|
100
|
+
get top() {
|
|
101
|
+
return this._top;
|
|
102
|
+
}
|
|
103
|
+
get right() {
|
|
104
|
+
return this._right;
|
|
105
|
+
}
|
|
106
|
+
get bottom() {
|
|
107
|
+
return this._bottom;
|
|
108
|
+
}
|
|
109
|
+
get left() {
|
|
110
|
+
return this._left;
|
|
111
|
+
}
|
|
112
|
+
get width() {
|
|
113
|
+
return Math.abs(this._left - this._right);
|
|
114
|
+
}
|
|
115
|
+
get height() {
|
|
116
|
+
return Math.abs(this._bottom - this._top);
|
|
117
|
+
}
|
|
118
|
+
equals({
|
|
119
|
+
top,
|
|
120
|
+
left,
|
|
121
|
+
bottom,
|
|
122
|
+
right
|
|
123
|
+
}) {
|
|
124
|
+
return top === this._top && bottom === this._bottom && left === this._left && right === this._right;
|
|
125
|
+
}
|
|
126
|
+
contains(target) {
|
|
127
|
+
if (isPoint(target)) {
|
|
128
|
+
const {
|
|
129
|
+
x,
|
|
130
|
+
y
|
|
131
|
+
} = target;
|
|
132
|
+
const isOnTopSide = y < this._top;
|
|
133
|
+
const isOnBottomSide = y > this._bottom;
|
|
134
|
+
const isOnLeftSide = x < this._left;
|
|
135
|
+
const isOnRightSide = x > this._right;
|
|
136
|
+
const result = !isOnTopSide && !isOnBottomSide && !isOnLeftSide && !isOnRightSide;
|
|
137
|
+
return {
|
|
138
|
+
reason: {
|
|
139
|
+
isOnBottomSide,
|
|
140
|
+
isOnLeftSide,
|
|
141
|
+
isOnRightSide,
|
|
142
|
+
isOnTopSide
|
|
143
|
+
},
|
|
144
|
+
result
|
|
145
|
+
};
|
|
146
|
+
} else {
|
|
147
|
+
const {
|
|
148
|
+
top,
|
|
149
|
+
left,
|
|
150
|
+
bottom,
|
|
151
|
+
right
|
|
152
|
+
} = target;
|
|
153
|
+
return top >= this._top && top <= this._bottom && bottom >= this._top && bottom <= this._bottom && left >= this._left && left <= this._right && right >= this._left && right <= this._right;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
intersectsWith(rect) {
|
|
157
|
+
const {
|
|
158
|
+
left: x1,
|
|
159
|
+
top: y1,
|
|
160
|
+
width: w1,
|
|
161
|
+
height: h1
|
|
162
|
+
} = rect;
|
|
163
|
+
const {
|
|
164
|
+
left: x2,
|
|
165
|
+
top: y2,
|
|
166
|
+
width: w2,
|
|
167
|
+
height: h2
|
|
168
|
+
} = this;
|
|
169
|
+
const maxX = x1 + w1 >= x2 + w2 ? x1 + w1 : x2 + w2;
|
|
170
|
+
const maxY = y1 + h1 >= y2 + h2 ? y1 + h1 : y2 + h2;
|
|
171
|
+
const minX = x1 <= x2 ? x1 : x2;
|
|
172
|
+
const minY = y1 <= y2 ? y1 : y2;
|
|
173
|
+
return maxX - minX <= w1 + w2 && maxY - minY <= h1 + h2;
|
|
174
|
+
}
|
|
175
|
+
generateNewRect({
|
|
176
|
+
left = this.left,
|
|
177
|
+
top = this.top,
|
|
178
|
+
right = this.right,
|
|
179
|
+
bottom = this.bottom
|
|
180
|
+
}) {
|
|
181
|
+
return new Rectangle(left, top, right, bottom);
|
|
182
|
+
}
|
|
183
|
+
static fromLTRB(left, top, right, bottom) {
|
|
184
|
+
return new Rectangle(left, top, right, bottom);
|
|
185
|
+
}
|
|
186
|
+
static fromLWTH(left, width, top, height) {
|
|
187
|
+
return new Rectangle(left, top, left + width, top + height);
|
|
188
|
+
}
|
|
189
|
+
static fromPoints(startPoint, endPoint) {
|
|
190
|
+
const {
|
|
191
|
+
y: top,
|
|
192
|
+
x: left
|
|
193
|
+
} = startPoint;
|
|
194
|
+
const {
|
|
195
|
+
y: bottom,
|
|
196
|
+
x: right
|
|
197
|
+
} = endPoint;
|
|
198
|
+
return Rectangle.fromLTRB(left, top, right, bottom);
|
|
199
|
+
}
|
|
200
|
+
static fromDOM(dom) {
|
|
201
|
+
const {
|
|
202
|
+
top,
|
|
203
|
+
width,
|
|
204
|
+
left,
|
|
205
|
+
height
|
|
206
|
+
} = dom.getBoundingClientRect();
|
|
207
|
+
return Rectangle.fromLWTH(left, width, top, height);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
213
|
+
*
|
|
214
|
+
* This source code is licensed under the MIT license found in the
|
|
215
|
+
* LICENSE file in the root directory of this source tree.
|
|
216
|
+
*
|
|
217
|
+
*/
|
|
218
|
+
|
|
219
|
+
const SPACE = 4;
|
|
220
|
+
const TARGET_LINE_HALF_HEIGHT = 2;
|
|
221
|
+
const DRAG_DATA_FORMAT = 'application/x-lexical-drag-block';
|
|
222
|
+
const TEXT_BOX_HORIZONTAL_PADDING = 28;
|
|
223
|
+
const BOX_INDENT_INSERT = 60;
|
|
224
|
+
const Downward = 1;
|
|
225
|
+
const Upward = -1;
|
|
226
|
+
const Indeterminate = 0;
|
|
227
|
+
let prevIndex = Infinity;
|
|
228
|
+
function getCurrentIndex(keysLength) {
|
|
229
|
+
if (keysLength === 0) {
|
|
230
|
+
return Infinity;
|
|
231
|
+
}
|
|
232
|
+
if (prevIndex >= 0 && prevIndex < keysLength) {
|
|
233
|
+
return prevIndex;
|
|
234
|
+
}
|
|
235
|
+
return Math.floor(keysLength / 2);
|
|
236
|
+
}
|
|
237
|
+
function getTopLevelNodeKeys(editor) {
|
|
238
|
+
return editor.getEditorState().read(() => traversal.dfs(lexical.$getRoot().getChildren(), node => {
|
|
239
|
+
if (node.getChildren) {
|
|
240
|
+
return node.getChildren().filter(node => onchainLexicalInstance.$isInstanceNode(node));
|
|
241
|
+
}
|
|
242
|
+
return [];
|
|
243
|
+
})).map(node => {
|
|
244
|
+
return node.getKey();
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
function getCollapsedMargins(elem) {
|
|
248
|
+
const getMargin = (element, margin) => element ? parseFloat(window.getComputedStyle(element)[margin]) : 0;
|
|
249
|
+
const {
|
|
250
|
+
marginTop,
|
|
251
|
+
marginBottom
|
|
252
|
+
} = window.getComputedStyle(elem);
|
|
253
|
+
const prevElemSiblingMarginBottom = getMargin(elem.previousElementSibling, 'marginBottom');
|
|
254
|
+
const nextElemSiblingMarginTop = getMargin(elem.nextElementSibling, 'marginTop');
|
|
255
|
+
const collapsedTopMargin = Math.max(parseFloat(marginTop), prevElemSiblingMarginBottom);
|
|
256
|
+
const collapsedBottomMargin = Math.max(parseFloat(marginBottom), nextElemSiblingMarginTop);
|
|
257
|
+
return {
|
|
258
|
+
marginBottom: collapsedBottomMargin,
|
|
259
|
+
marginTop: collapsedTopMargin
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function getBlockElement(anchorElem, editor, event, useEdgeAsDefault = false) {
|
|
263
|
+
const anchorElementRect = anchorElem.getBoundingClientRect();
|
|
264
|
+
const topLevelNodeKeys = getTopLevelNodeKeys(editor);
|
|
265
|
+
let blockElem = null;
|
|
266
|
+
editor.getEditorState().read(() => {
|
|
267
|
+
if (useEdgeAsDefault) {
|
|
268
|
+
const [firstNode, lastNode] = [editor.getElementByKey(topLevelNodeKeys[0]), editor.getElementByKey(topLevelNodeKeys[topLevelNodeKeys.length - 1])];
|
|
269
|
+
const [firstNodeRect, lastNodeRect] = [firstNode != null ? firstNode.getBoundingClientRect() : undefined, lastNode != null ? lastNode.getBoundingClientRect() : undefined];
|
|
270
|
+
if (firstNodeRect && lastNodeRect) {
|
|
271
|
+
const firstNodeZoom = utils.calculateZoomLevel(firstNode);
|
|
272
|
+
const lastNodeZoom = utils.calculateZoomLevel(lastNode);
|
|
273
|
+
if (event.y / firstNodeZoom < firstNodeRect.top) {
|
|
274
|
+
blockElem = firstNode;
|
|
275
|
+
} else if (event.y / lastNodeZoom > lastNodeRect.bottom) {
|
|
276
|
+
blockElem = lastNode;
|
|
277
|
+
}
|
|
278
|
+
if (blockElem) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
// 直接获取第一个有instance属性的祖先元素 并返回
|
|
284
|
+
if (event.target instanceof Element) {
|
|
285
|
+
blockElem = event.target.closest(`[instance=true]`);
|
|
286
|
+
return blockElem;
|
|
287
|
+
}
|
|
288
|
+
let index = getCurrentIndex(topLevelNodeKeys.length);
|
|
289
|
+
let direction = Indeterminate;
|
|
290
|
+
while (index >= 0 && index < topLevelNodeKeys.length) {
|
|
291
|
+
const key = topLevelNodeKeys[index];
|
|
292
|
+
const elem = editor.getElementByKey(key);
|
|
293
|
+
if (elem === null) {
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
const zoom = utils.calculateZoomLevel(elem);
|
|
297
|
+
const point = new Point(event.x / zoom, event.y / zoom);
|
|
298
|
+
const domRect = Rectangle.fromDOM(elem);
|
|
299
|
+
// console.log({direction, elem, index, zoom, point, domRect}, "elem")
|
|
300
|
+
const {
|
|
301
|
+
marginTop,
|
|
302
|
+
marginBottom
|
|
303
|
+
} = getCollapsedMargins(elem);
|
|
304
|
+
const rect = domRect.generateNewRect({
|
|
305
|
+
bottom: domRect.bottom + marginBottom,
|
|
306
|
+
left: anchorElementRect.left,
|
|
307
|
+
right: anchorElementRect.right,
|
|
308
|
+
top: domRect.top - marginTop
|
|
309
|
+
});
|
|
310
|
+
const {
|
|
311
|
+
result,
|
|
312
|
+
reason: {
|
|
313
|
+
isOnTopSide,
|
|
314
|
+
isOnBottomSide
|
|
315
|
+
}
|
|
316
|
+
} = rect.contains(point);
|
|
317
|
+
if (result) {
|
|
318
|
+
blockElem = elem;
|
|
319
|
+
prevIndex = index;
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
if (direction === Indeterminate) {
|
|
323
|
+
if (isOnTopSide) {
|
|
324
|
+
direction = Upward;
|
|
325
|
+
} else if (isOnBottomSide) {
|
|
326
|
+
direction = Downward;
|
|
327
|
+
} else {
|
|
328
|
+
// stop search block element
|
|
329
|
+
direction = Infinity;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
index += direction;
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
return blockElem;
|
|
336
|
+
}
|
|
337
|
+
function setMenuPosition(targetElem, floatingElem, anchorElem) {
|
|
338
|
+
if (!targetElem) {
|
|
339
|
+
floatingElem.style.opacity = '0';
|
|
340
|
+
floatingElem.style.transform = 'translate(-10000px, -10000px)';
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
const targetRect = targetElem.getBoundingClientRect();
|
|
344
|
+
window.getComputedStyle(targetElem);
|
|
345
|
+
// const floatingElemRect = floatingElem.getBoundingClientRect();
|
|
346
|
+
const anchorElementRect = anchorElem.getBoundingClientRect();
|
|
347
|
+
|
|
348
|
+
// [源码修改]
|
|
349
|
+
// const top =
|
|
350
|
+
// targetRect.top +
|
|
351
|
+
// (targetCalculateHeight - floatingElemRect.height) / 2 -
|
|
352
|
+
// anchorElementRect.top;
|
|
353
|
+
const top = targetRect.top - anchorElementRect.top;
|
|
354
|
+
const left = SPACE;
|
|
355
|
+
floatingElem.style.opacity = '1';
|
|
356
|
+
floatingElem.style.transform = `translate(${left}px, ${top}px)`;
|
|
357
|
+
}
|
|
358
|
+
function setDragImage(dataTransfer, draggableBlockElem) {
|
|
359
|
+
const {
|
|
360
|
+
transform
|
|
361
|
+
} = draggableBlockElem.style;
|
|
362
|
+
|
|
363
|
+
// Remove dragImage borders
|
|
364
|
+
draggableBlockElem.style.transform = 'translateZ(0)';
|
|
365
|
+
dataTransfer.setDragImage(draggableBlockElem, 0, 0);
|
|
366
|
+
setTimeout(() => {
|
|
367
|
+
draggableBlockElem.style.transform = transform;
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/** 放置区域是否是自身 */
|
|
372
|
+
// function isPlacementAreaElemSelf(
|
|
373
|
+
// targetBlockElem: HTMLElement,
|
|
374
|
+
// latestDraggableBlockElem: React.MutableRefObject<HTMLElement | null>,
|
|
375
|
+
// ) {
|
|
376
|
+
// const isTargetSelf = latestDraggableBlockElem.current === targetBlockElem;
|
|
377
|
+
// return isTargetSelf;
|
|
378
|
+
// }
|
|
379
|
+
|
|
380
|
+
/** 放置区域是否是自身的子级 */
|
|
381
|
+
function $isPlacementAreaElemSelfRoChild(targetBlockElem, latestDraggableBlockElem, isPlacementDown = true) {
|
|
382
|
+
const key = targetBlockElem.getAttribute('key');
|
|
383
|
+
let node = key ? lexical.$getNodeByKey(key) : null;
|
|
384
|
+
if (!isPlacementDown) {
|
|
385
|
+
node = node ? node.getPreviousSibling() : null;
|
|
386
|
+
}
|
|
387
|
+
if (node) {
|
|
388
|
+
return Boolean(utils.$findMatchingParent(node, current => {
|
|
389
|
+
const draggable = latestDraggableBlockElem.current;
|
|
390
|
+
if (current && draggable) {
|
|
391
|
+
return current.getKey() === draggable.getAttribute('key');
|
|
392
|
+
}
|
|
393
|
+
return false;
|
|
394
|
+
}));
|
|
395
|
+
}
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/** 是否是插入到子节点 */
|
|
400
|
+
function isInsertChild(targetBlockMouseX) {
|
|
401
|
+
const isIndent = targetBlockMouseX > BOX_INDENT_INSERT;
|
|
402
|
+
return isIndent;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/** 是否是放到节点下面 */
|
|
406
|
+
function isPlacementAreaDown(mouseY, targetBlockElemTop) {
|
|
407
|
+
return mouseY >= targetBlockElemTop + 40;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/** 获取鼠标位置以及一些基础数据 */
|
|
411
|
+
function getMouseInfo({
|
|
412
|
+
target,
|
|
413
|
+
pageY,
|
|
414
|
+
pageX,
|
|
415
|
+
targetBlockElem
|
|
416
|
+
}) {
|
|
417
|
+
const mouseY = pageY / utils.calculateZoomLevel(target);
|
|
418
|
+
const mouseX = pageX / utils.calculateZoomLevel(target);
|
|
419
|
+
const targetBlockElemDOMRect = targetBlockElem.getBoundingClientRect();
|
|
420
|
+
const {
|
|
421
|
+
left: targetBlockElemLeft
|
|
422
|
+
} = targetBlockElemDOMRect;
|
|
423
|
+
const targetBlockMouseX = mouseX - targetBlockElemLeft;
|
|
424
|
+
const isAddChild = isInsertChild(targetBlockMouseX);
|
|
425
|
+
return {
|
|
426
|
+
isAddChild,
|
|
427
|
+
mouseX,
|
|
428
|
+
mouseY,
|
|
429
|
+
targetBlockElemDOMRect
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
function setTargetLine(params) {
|
|
433
|
+
const {
|
|
434
|
+
targetBlockElem,
|
|
435
|
+
targetLineElem,
|
|
436
|
+
mouseY,
|
|
437
|
+
mouseX,
|
|
438
|
+
anchorElem,
|
|
439
|
+
targetLineIndent,
|
|
440
|
+
targetBlockElemDOMRect
|
|
441
|
+
} = params;
|
|
442
|
+
const {
|
|
443
|
+
top: targetBlockElemTop,
|
|
444
|
+
height: targetBlockElemHeight,
|
|
445
|
+
left: targetBlockElemLeft,
|
|
446
|
+
width: targetBlockElemWidth
|
|
447
|
+
} = targetBlockElemDOMRect || targetBlockElem.getBoundingClientRect();
|
|
448
|
+
const {
|
|
449
|
+
top: anchorTop
|
|
450
|
+
} = anchorElem.getBoundingClientRect();
|
|
451
|
+
const {
|
|
452
|
+
marginTop,
|
|
453
|
+
marginBottom
|
|
454
|
+
} = getCollapsedMargins(targetBlockElem);
|
|
455
|
+
let lineTop = targetBlockElemTop;
|
|
456
|
+
if (isPlacementAreaDown(mouseY, targetBlockElemTop)) {
|
|
457
|
+
lineTop += targetBlockElemHeight + marginBottom / 2;
|
|
458
|
+
} else {
|
|
459
|
+
lineTop -= marginTop / 2;
|
|
460
|
+
}
|
|
461
|
+
const targetBlockMouseX = mouseX - targetBlockElemLeft;
|
|
462
|
+
const top = lineTop - anchorTop - TARGET_LINE_HALF_HEIGHT;
|
|
463
|
+
const lineIndent = targetLineIndent || TEXT_BOX_HORIZONTAL_PADDING;
|
|
464
|
+
const isAddChild = isInsertChild(targetBlockMouseX);
|
|
465
|
+
const left = isAddChild ? BOX_INDENT_INSERT + lineIndent : lineIndent;
|
|
466
|
+
targetLineElem.style.transform = `translate(${left}px, ${top}px)`;
|
|
467
|
+
targetLineElem.style.backgroundColor = isAddChild ? 'purple' : 'deepskyblue';
|
|
468
|
+
targetLineElem.style.width = `${targetBlockElemWidth - (isAddChild ? BOX_INDENT_INSERT : 0)}px`;
|
|
469
|
+
targetLineElem.style.opacity = '.4';
|
|
470
|
+
}
|
|
471
|
+
function hideTargetLine(targetLineElem) {
|
|
472
|
+
if (targetLineElem) {
|
|
473
|
+
targetLineElem.style.opacity = '0';
|
|
474
|
+
targetLineElem.style.transform = 'translate(-10000px, -10000px)';
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
function useDraggableBlockMenu(editor, anchorElem, menuRef, targetLineRef, isEditable, menuComponent, targetLineComponent, isOnMenu, targetLineIndent, onElementChanged) {
|
|
478
|
+
const scrollerElem = anchorElem.parentElement;
|
|
479
|
+
const isDraggingBlockRef = react.useRef(false);
|
|
480
|
+
const [draggableBlockElem, setDraggableBlockElemState] = react.useState(null);
|
|
481
|
+
const latestDraggableBlockElem = useLatest(draggableBlockElem);
|
|
482
|
+
const setDraggableBlockElem = react.useCallback(elem => {
|
|
483
|
+
setDraggableBlockElemState(elem);
|
|
484
|
+
if (onElementChanged) {
|
|
485
|
+
onElementChanged(elem);
|
|
486
|
+
}
|
|
487
|
+
}, [onElementChanged]);
|
|
488
|
+
react.useEffect(() => {
|
|
489
|
+
function onMouseMove(event) {
|
|
490
|
+
const target = event.target;
|
|
491
|
+
if (!utils.isHTMLElement(target)) {
|
|
492
|
+
setDraggableBlockElem(null);
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
if (isOnMenu(target)) {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
const _draggableBlockElem = getBlockElement(anchorElem, editor, event);
|
|
499
|
+
setDraggableBlockElem(_draggableBlockElem);
|
|
500
|
+
}
|
|
501
|
+
function onMouseLeave() {
|
|
502
|
+
setDraggableBlockElem(null);
|
|
503
|
+
}
|
|
504
|
+
if (scrollerElem != null) {
|
|
505
|
+
scrollerElem.addEventListener('mousemove', onMouseMove);
|
|
506
|
+
scrollerElem.addEventListener('mouseleave', onMouseLeave);
|
|
507
|
+
}
|
|
508
|
+
return () => {
|
|
509
|
+
if (scrollerElem != null) {
|
|
510
|
+
scrollerElem.removeEventListener('mousemove', onMouseMove);
|
|
511
|
+
scrollerElem.removeEventListener('mouseleave', onMouseLeave);
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
}, [scrollerElem, anchorElem, editor, isOnMenu, setDraggableBlockElem]);
|
|
515
|
+
react.useEffect(() => {
|
|
516
|
+
if (menuRef.current) {
|
|
517
|
+
// 更新操作栏区域
|
|
518
|
+
setMenuPosition(draggableBlockElem, menuRef.current, anchorElem);
|
|
519
|
+
}
|
|
520
|
+
}, [anchorElem, draggableBlockElem, menuRef]);
|
|
521
|
+
react.useEffect(() => {
|
|
522
|
+
// 拖动
|
|
523
|
+
function $onDragover(event) {
|
|
524
|
+
if (!isDraggingBlockRef.current) {
|
|
525
|
+
return false;
|
|
526
|
+
}
|
|
527
|
+
const [isFileTransfer] = richText.eventFiles(event);
|
|
528
|
+
if (isFileTransfer) {
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
const {
|
|
532
|
+
pageY,
|
|
533
|
+
pageX,
|
|
534
|
+
target
|
|
535
|
+
} = event;
|
|
536
|
+
if (!utils.isHTMLElement(target)) {
|
|
537
|
+
return false;
|
|
538
|
+
}
|
|
539
|
+
const targetBlockElem = getBlockElement(anchorElem, editor, event, true);
|
|
540
|
+
const targetLineElem = targetLineRef.current;
|
|
541
|
+
if (targetBlockElem === null || targetLineElem === null) {
|
|
542
|
+
return false;
|
|
543
|
+
}
|
|
544
|
+
const {
|
|
545
|
+
mouseY,
|
|
546
|
+
mouseX,
|
|
547
|
+
isAddChild,
|
|
548
|
+
targetBlockElemDOMRect
|
|
549
|
+
} = getMouseInfo({
|
|
550
|
+
pageX,
|
|
551
|
+
pageY,
|
|
552
|
+
target,
|
|
553
|
+
targetBlockElem
|
|
554
|
+
});
|
|
555
|
+
const isPlacementDown = isPlacementAreaDown(mouseY, targetBlockElemDOMRect.top);
|
|
556
|
+
const isPlacementSelfRoChild = $isPlacementAreaElemSelfRoChild(targetBlockElem, latestDraggableBlockElem, isPlacementDown);
|
|
557
|
+
if (isPlacementSelfRoChild) {
|
|
558
|
+
if (isAddChild) {
|
|
559
|
+
hideTargetLine(targetLineElem);
|
|
560
|
+
return false;
|
|
561
|
+
}
|
|
562
|
+
const key = targetBlockElem.getAttribute('key');
|
|
563
|
+
const targetNode = key ? lexical.$getNodeByKey(key) : null;
|
|
564
|
+
if (!targetNode || !lexical.$isRootNode(targetNode.getParent())) {
|
|
565
|
+
hideTargetLine(targetLineElem);
|
|
566
|
+
return false;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
setTargetLine({
|
|
570
|
+
anchorElem,
|
|
571
|
+
mouseX,
|
|
572
|
+
mouseY,
|
|
573
|
+
targetBlockElem,
|
|
574
|
+
targetBlockElemDOMRect,
|
|
575
|
+
targetLineElem,
|
|
576
|
+
targetLineIndent
|
|
577
|
+
});
|
|
578
|
+
// Prevent default event to be able to trigger onDrop events
|
|
579
|
+
event.preventDefault();
|
|
580
|
+
return true;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// 放下
|
|
584
|
+
function $onDrop(event) {
|
|
585
|
+
if (!isDraggingBlockRef.current) {
|
|
586
|
+
return false;
|
|
587
|
+
}
|
|
588
|
+
const [isFileTransfer] = richText.eventFiles(event);
|
|
589
|
+
if (isFileTransfer) {
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
const {
|
|
593
|
+
target,
|
|
594
|
+
dataTransfer,
|
|
595
|
+
pageY,
|
|
596
|
+
pageX
|
|
597
|
+
} = event;
|
|
598
|
+
const dragData = dataTransfer != null ? dataTransfer.getData(DRAG_DATA_FORMAT) : '';
|
|
599
|
+
const draggedNode = lexical.$getNodeByKey(dragData);
|
|
600
|
+
if (!draggedNode) {
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
if (!utils.isHTMLElement(target)) {
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
const targetBlockElem = getBlockElement(anchorElem, editor, event, true);
|
|
607
|
+
if (!targetBlockElem) {
|
|
608
|
+
return false;
|
|
609
|
+
}
|
|
610
|
+
const targetNode = lexical.$getNearestNodeFromDOMNode(targetBlockElem);
|
|
611
|
+
if (!targetNode) {
|
|
612
|
+
return false;
|
|
613
|
+
}
|
|
614
|
+
const {
|
|
615
|
+
mouseY,
|
|
616
|
+
isAddChild,
|
|
617
|
+
targetBlockElemDOMRect: {
|
|
618
|
+
top: targetBlockElemTop
|
|
619
|
+
}
|
|
620
|
+
} = getMouseInfo({
|
|
621
|
+
pageX,
|
|
622
|
+
pageY,
|
|
623
|
+
target,
|
|
624
|
+
targetBlockElem
|
|
625
|
+
});
|
|
626
|
+
if (targetNode === draggedNode && !isAddChild) {
|
|
627
|
+
return true;
|
|
628
|
+
}
|
|
629
|
+
if (isPlacementAreaDown(mouseY, targetBlockElemTop)) {
|
|
630
|
+
if (isAddChild) {
|
|
631
|
+
if (onchainLexicalInstance.$isInstanceNode(targetNode)) {
|
|
632
|
+
targetNode.append(draggedNode);
|
|
633
|
+
}
|
|
634
|
+
} else {
|
|
635
|
+
targetNode.insertAfter(draggedNode);
|
|
636
|
+
}
|
|
637
|
+
} else {
|
|
638
|
+
if (isAddChild && lexical.$isElementNode(targetNode)) {
|
|
639
|
+
const previous = targetNode.getPreviousSibling();
|
|
640
|
+
if (onchainLexicalInstance.$isInstanceNode(previous)) {
|
|
641
|
+
previous.append(draggedNode);
|
|
642
|
+
}
|
|
643
|
+
} else {
|
|
644
|
+
targetNode.insertBefore(draggedNode);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
draggedNode.selectEnd();
|
|
648
|
+
setDraggableBlockElem(null);
|
|
649
|
+
return true;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// 添加拖拽事件
|
|
653
|
+
return utils.mergeRegister(editor.registerCommand(lexical.DRAGOVER_COMMAND, event => {
|
|
654
|
+
const result = $onDragover(event);
|
|
655
|
+
if (!result) {
|
|
656
|
+
if (event.dataTransfer) {
|
|
657
|
+
event.dataTransfer.dropEffect = 'none';
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return result;
|
|
661
|
+
}, lexical.COMMAND_PRIORITY_LOW), editor.registerCommand(lexical.DROP_COMMAND, event => {
|
|
662
|
+
return $onDrop(event);
|
|
663
|
+
}, lexical.COMMAND_PRIORITY_HIGH));
|
|
664
|
+
}, [anchorElem, editor, targetLineRef, setDraggableBlockElem, targetLineIndent, latestDraggableBlockElem]);
|
|
665
|
+
function onDragStart(event) {
|
|
666
|
+
const dataTransfer = event.dataTransfer;
|
|
667
|
+
if (!dataTransfer || !draggableBlockElem) {
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
setDragImage(dataTransfer, draggableBlockElem);
|
|
671
|
+
let nodeKey = '';
|
|
672
|
+
editor.update(() => {
|
|
673
|
+
const node = lexical.$getNearestNodeFromDOMNode(draggableBlockElem);
|
|
674
|
+
if (node) {
|
|
675
|
+
nodeKey = node.getKey();
|
|
676
|
+
}
|
|
677
|
+
});
|
|
678
|
+
isDraggingBlockRef.current = true;
|
|
679
|
+
dataTransfer.setData(DRAG_DATA_FORMAT, nodeKey);
|
|
680
|
+
}
|
|
681
|
+
function onDragEnd() {
|
|
682
|
+
isDraggingBlockRef.current = false;
|
|
683
|
+
hideTargetLine(targetLineRef.current);
|
|
684
|
+
}
|
|
685
|
+
return /*#__PURE__*/reactDom.createPortal(/*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, {
|
|
686
|
+
children: [/*#__PURE__*/jsxRuntime.jsx("div", {
|
|
687
|
+
draggable: true,
|
|
688
|
+
onDragStart: onDragStart,
|
|
689
|
+
onDragEnd: onDragEnd,
|
|
690
|
+
children: isEditable && menuComponent
|
|
691
|
+
}), targetLineComponent]
|
|
692
|
+
}), anchorElem);
|
|
693
|
+
}
|
|
694
|
+
function DraggableBlockPlugin_EXPERIMENTAL({
|
|
695
|
+
anchorElem = document.body,
|
|
696
|
+
menuRef,
|
|
697
|
+
targetLineRef,
|
|
698
|
+
menuComponent,
|
|
699
|
+
targetLineComponent,
|
|
700
|
+
targetLineIndent,
|
|
701
|
+
isOnMenu,
|
|
702
|
+
onElementChanged
|
|
703
|
+
}) {
|
|
704
|
+
const [editor] = LexicalComposerContext.useLexicalComposerContext();
|
|
705
|
+
return useDraggableBlockMenu(editor, anchorElem, menuRef, targetLineRef, editor._editable, menuComponent, targetLineComponent, isOnMenu, targetLineIndent, onElementChanged);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function styleInject(css, ref) {
|
|
709
|
+
if (ref === void 0) ref = {};
|
|
710
|
+
var insertAt = ref.insertAt;
|
|
711
|
+
if (typeof document === 'undefined') {
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
var head = document.head || document.getElementsByTagName('head')[0];
|
|
715
|
+
var style = document.createElement('style');
|
|
716
|
+
style.type = 'text/css';
|
|
717
|
+
if (insertAt === 'top') {
|
|
718
|
+
if (head.firstChild) {
|
|
719
|
+
head.insertBefore(style, head.firstChild);
|
|
720
|
+
} else {
|
|
721
|
+
head.appendChild(style);
|
|
722
|
+
}
|
|
723
|
+
} else {
|
|
724
|
+
head.appendChild(style);
|
|
725
|
+
}
|
|
726
|
+
if (style.styleSheet) {
|
|
727
|
+
style.styleSheet.cssText = css;
|
|
728
|
+
} else {
|
|
729
|
+
style.appendChild(document.createTextNode(css));
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
var css_248z = ".index-module_draggable-block-menu__LSGkG{align-items:center;border-radius:4px;cursor:grab;display:flex;gap:2px;height:40px;left:0;opacity:0;position:absolute;top:0;will-change:transform}.index-module_draggable-block-menu__LSGkG .index-module_icon__J-jv8{align-items:center;background-image:url(/richText/draggable-block-menu.svg);display:flex;height:16px;justify-content:center;opacity:.3;width:16px}.index-module_draggable-block-menu__LSGkG .index-module_icon__J-jv8:hover{background-color:#efefef}.index-module_draggable-block-menu__LSGkG .index-module_icon-plus__jGImy{background-color:transparent;background-image:url(/richText/plus.svg);border:none;cursor:pointer;display:inline-block}.index-module_draggable-block-menu__LSGkG:active{cursor:grabbing}.index-module_draggable-block-target-line__iSspi{background:#00bfff;height:4px;left:0;opacity:0;pointer-events:none;position:absolute;top:0;will-change:transform}";
|
|
734
|
+
var Styles = {"draggable-block-menu":"index-module_draggable-block-menu__LSGkG","icon":"index-module_icon__J-jv8","icon-plus":"index-module_icon-plus__jGImy","draggable-block-target-line":"index-module_draggable-block-target-line__iSspi"};
|
|
735
|
+
styleInject(css_248z);
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
739
|
+
*
|
|
740
|
+
* This source code is licensed under the MIT license found in the
|
|
741
|
+
* LICENSE file in the root directory of this source tree.
|
|
742
|
+
*
|
|
743
|
+
*/
|
|
744
|
+
|
|
745
|
+
const DRAGGABLE_BLOCK_MENU_CLASSNAME = Styles['draggable-block-menu'];
|
|
746
|
+
function isOnMenu(element) {
|
|
747
|
+
return !!element.closest(`.${DRAGGABLE_BLOCK_MENU_CLASSNAME}`);
|
|
748
|
+
}
|
|
749
|
+
function DraggableBlockPlugin({
|
|
750
|
+
anchorElem = document.body,
|
|
751
|
+
dragIcon,
|
|
752
|
+
targetLineIndent
|
|
753
|
+
}) {
|
|
754
|
+
const [editor] = LexicalComposerContext.useLexicalComposerContext();
|
|
755
|
+
const menuRef = react.useRef(null);
|
|
756
|
+
const targetLineRef = react.useRef(null);
|
|
757
|
+
const [draggableElement, setDraggableElement] = react.useState(null);
|
|
758
|
+
function insertBlock(e) {
|
|
759
|
+
if (!draggableElement || !editor) {
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
editor.update(() => {
|
|
763
|
+
const node = lexical.$getNearestNodeFromDOMNode(draggableElement);
|
|
764
|
+
if (!node) {
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
const pNode = onchainLexicalInstance.$createInstanceNode();
|
|
768
|
+
if (e.altKey || e.ctrlKey) {
|
|
769
|
+
node.insertBefore(pNode);
|
|
770
|
+
} else {
|
|
771
|
+
node.insertAfter(pNode);
|
|
772
|
+
}
|
|
773
|
+
pNode.select();
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
return /*#__PURE__*/jsxRuntime.jsx(DraggableBlockPlugin_EXPERIMENTAL, {
|
|
777
|
+
anchorElem: anchorElem,
|
|
778
|
+
menuRef: menuRef,
|
|
779
|
+
targetLineRef: targetLineRef,
|
|
780
|
+
menuComponent: /*#__PURE__*/jsxRuntime.jsxs("div", {
|
|
781
|
+
ref: menuRef,
|
|
782
|
+
className: `${Styles.icon} ${Styles['draggable-block-menu']}`,
|
|
783
|
+
children: [/*#__PURE__*/jsxRuntime.jsx("button", {
|
|
784
|
+
title: "Click to add below",
|
|
785
|
+
className: `${Styles.icon} ${Styles['icon-plus']}`,
|
|
786
|
+
onClick: insertBlock,
|
|
787
|
+
style: {
|
|
788
|
+
display: 'none'
|
|
789
|
+
}
|
|
790
|
+
}), /*#__PURE__*/jsxRuntime.jsx("div", {
|
|
791
|
+
className: Styles.icon,
|
|
792
|
+
children: dragIcon
|
|
793
|
+
})]
|
|
794
|
+
}),
|
|
795
|
+
targetLineComponent: /*#__PURE__*/jsxRuntime.jsx("div", {
|
|
796
|
+
ref: targetLineRef,
|
|
797
|
+
className: `${Styles['draggable-block-target-line']}`
|
|
798
|
+
}),
|
|
799
|
+
targetLineIndent: targetLineIndent,
|
|
800
|
+
isOnMenu: isOnMenu,
|
|
801
|
+
onElementChanged: setDraggableElement
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
exports.default = DraggableBlockPlugin;
|