Skip to content

Commit 79b6261

Browse files
authored
fix dragimage + allow dragging next to editor (#110)
1 parent e310448 commit 79b6261

File tree

1 file changed

+149
-87
lines changed

1 file changed

+149
-87
lines changed

packages/core/src/extensions/DraggableBlocks/DraggableBlocksPlugin.ts

Lines changed: 149 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,10 @@ function setDragImage(view: EditorView, from: number, to = from) {
150150
}
151151

152152
// dataTransfer.setDragImage(element) only works if element is attached to the DOM.
153+
unsetDragImage();
153154
dragImageElement = parentClone;
154-
dragImageElement.className = styles.dragPreview;
155+
dragImageElement.className =
156+
dragImageElement.className + " " + styles.dragPreview;
155157
document.body.appendChild(dragImageElement);
156158
}
157159

@@ -245,104 +247,164 @@ export class BlockMenuView {
245247

246248
this.blockMenu = blockMenuFactory(this.getStaticParams());
247249

250+
document.body.addEventListener("drop", this.onDrop, true);
251+
document.body.addEventListener("dragover", this.onDragOver);
252+
248253
// Shows or updates menu position whenever the cursor moves, if the menu isn't frozen.
249-
document.body.addEventListener(
250-
"mousemove",
251-
(event) => {
252-
if (this.menuFrozen) {
253-
return;
254-
}
255-
256-
// Editor itself may have padding or other styling which affects size/position, so we get the boundingRect of
257-
// the first child (i.e. the blockGroup that wraps all blocks in the editor) for a more accurate bounding box.
258-
const editorBoundingBox = (
259-
this.editor.view.dom.firstChild! as HTMLElement
260-
).getBoundingClientRect();
261-
262-
this.horizontalPosAnchor = editorBoundingBox.x;
263-
264-
// Gets block at mouse cursor's vertical position.
265-
const coords = {
266-
left: editorBoundingBox.left + editorBoundingBox.width / 2, // take middle of editor
267-
top: event.clientY,
268-
};
269-
const block = getDraggableBlockFromCoords(coords, this.editor.view);
270-
271-
// Closes the menu if the mouse cursor is beyond the editor vertically.
272-
if (!block) {
273-
if (this.menuOpen) {
274-
this.menuOpen = false;
275-
this.blockMenu.hide();
276-
}
277-
278-
return;
279-
}
280-
281-
// Doesn't update if the menu is already open and the mouse cursor is still hovering the same block.
282-
if (
283-
this.menuOpen &&
284-
this.hoveredBlockContent?.hasAttribute("data-id") &&
285-
this.hoveredBlockContent?.getAttribute("data-id") === block.id
286-
) {
287-
return;
288-
}
289-
290-
// Gets the block's content node, which lets to ignore child blocks when determining the block menu's position.
291-
const blockContent = block.node.firstChild as HTMLElement;
292-
this.hoveredBlockContent = blockContent;
293-
294-
if (!blockContent) {
295-
return;
296-
}
297-
298-
// Shows or updates elements.
299-
if (!this.menuOpen) {
300-
this.menuOpen = true;
301-
this.blockMenu.render(this.getDynamicParams(), true);
302-
} else {
303-
this.blockMenu.render(this.getDynamicParams(), false);
304-
}
305-
},
306-
true
307-
);
254+
document.body.addEventListener("mousemove", this.onMouseMove, true);
308255

309256
// Hides and unfreezes the menu whenever the user selects the editor with the mouse or presses a key.
310257
// TODO: Better integration with suggestions menu and only editor scope?
311-
document.body.addEventListener(
312-
"mousedown",
313-
(event) => {
314-
if (this.blockMenu.element?.contains(event.target as HTMLElement)) {
315-
return;
316-
}
317-
318-
if (this.menuOpen) {
319-
this.menuOpen = false;
320-
this.blockMenu.hide();
321-
}
258+
document.body.addEventListener("mousedown", this.onMouseDown, true);
259+
document.body.addEventListener("keydown", this.onKeyDown, true);
260+
}
322261

323-
this.menuFrozen = false;
324-
},
325-
true
326-
);
327-
document.body.addEventListener(
328-
"keydown",
329-
() => {
330-
if (this.menuOpen) {
331-
this.menuOpen = false;
332-
this.blockMenu.hide();
333-
}
262+
/**
263+
* If the event is outside of the editor contents,
264+
* we dispatch a fake event, so that we can still drop the content
265+
* when dragging / dropping to the side of the editor
266+
*/
267+
onDrop = (event: DragEvent) => {
268+
if ((event as any).synthetic) {
269+
return;
270+
}
271+
let pos = this.editor.view.posAtCoords({
272+
left: event.clientX,
273+
top: event.clientY,
274+
});
334275

335-
this.menuFrozen = false;
336-
},
337-
true
338-
);
339-
}
276+
if (!pos || pos.inside === -1) {
277+
const evt = new Event("drop", event) as any;
278+
const editorBoundingBox = (
279+
this.editor.view.dom.firstChild! as HTMLElement
280+
).getBoundingClientRect();
281+
evt.clientX = editorBoundingBox.left + editorBoundingBox.width / 2;
282+
evt.clientY = event.clientY;
283+
evt.dataTransfer = event.dataTransfer;
284+
evt.preventDefault = () => event.preventDefault();
285+
evt.synthetic = true; // prevent recursion
286+
// console.log("dispatch fake drop");
287+
this.editor.view.dom.dispatchEvent(evt);
288+
}
289+
};
290+
291+
/**
292+
* If the event is outside of the editor contents,
293+
* we dispatch a fake event, so that we can still drop the content
294+
* when dragging / dropping to the side of the editor
295+
*/
296+
onDragOver = (event: DragEvent) => {
297+
if ((event as any).synthetic) {
298+
return;
299+
}
300+
let pos = this.editor.view.posAtCoords({
301+
left: event.clientX,
302+
top: event.clientY,
303+
});
304+
305+
if (!pos || pos.inside === -1) {
306+
const evt = new Event("dragover", event) as any;
307+
const editorBoundingBox = (
308+
this.editor.view.dom.firstChild! as HTMLElement
309+
).getBoundingClientRect();
310+
evt.clientX = editorBoundingBox.left + editorBoundingBox.width / 2;
311+
evt.clientY = event.clientY;
312+
evt.dataTransfer = event.dataTransfer;
313+
evt.preventDefault = () => event.preventDefault();
314+
evt.synthetic = true; // prevent recursion
315+
// console.log("dispatch fake dragover");
316+
this.editor.view.dom.dispatchEvent(evt);
317+
}
318+
};
319+
320+
onKeyDown = (_event: KeyboardEvent) => {
321+
if (this.menuOpen) {
322+
this.menuOpen = false;
323+
this.blockMenu.hide();
324+
}
325+
326+
this.menuFrozen = false;
327+
};
328+
329+
onMouseDown = (event: MouseEvent) => {
330+
if (this.blockMenu.element?.contains(event.target as HTMLElement)) {
331+
return;
332+
}
333+
334+
if (this.menuOpen) {
335+
this.menuOpen = false;
336+
this.blockMenu.hide();
337+
}
338+
339+
this.menuFrozen = false;
340+
};
341+
342+
onMouseMove = (event: MouseEvent) => {
343+
if (this.menuFrozen) {
344+
return;
345+
}
346+
347+
// Editor itself may have padding or other styling which affects size/position, so we get the boundingRect of
348+
// the first child (i.e. the blockGroup that wraps all blocks in the editor) for a more accurate bounding box.
349+
const editorBoundingBox = (
350+
this.editor.view.dom.firstChild! as HTMLElement
351+
).getBoundingClientRect();
352+
353+
this.horizontalPosAnchor = editorBoundingBox.x;
354+
355+
// Gets block at mouse cursor's vertical position.
356+
const coords = {
357+
left: editorBoundingBox.left + editorBoundingBox.width / 2, // take middle of editor
358+
top: event.clientY,
359+
};
360+
const block = getDraggableBlockFromCoords(coords, this.editor.view);
361+
362+
// Closes the menu if the mouse cursor is beyond the editor vertically.
363+
if (!block) {
364+
if (this.menuOpen) {
365+
this.menuOpen = false;
366+
this.blockMenu.hide();
367+
}
368+
369+
return;
370+
}
371+
372+
// Doesn't update if the menu is already open and the mouse cursor is still hovering the same block.
373+
if (
374+
this.menuOpen &&
375+
this.hoveredBlockContent?.hasAttribute("data-id") &&
376+
this.hoveredBlockContent?.getAttribute("data-id") === block.id
377+
) {
378+
return;
379+
}
380+
381+
// Gets the block's content node, which lets to ignore child blocks when determining the block menu's position.
382+
const blockContent = block.node.firstChild as HTMLElement;
383+
this.hoveredBlockContent = blockContent;
384+
385+
if (!blockContent) {
386+
return;
387+
}
388+
389+
// Shows or updates elements.
390+
if (!this.menuOpen) {
391+
this.menuOpen = true;
392+
this.blockMenu.render(this.getDynamicParams(), true);
393+
} else {
394+
this.blockMenu.render(this.getDynamicParams(), false);
395+
}
396+
};
340397

341398
destroy() {
342399
if (this.menuOpen) {
343400
this.menuOpen = false;
344401
this.blockMenu.hide();
345402
}
403+
document.body.removeEventListener("mousemove", this.onMouseMove);
404+
document.body.removeEventListener("dragover", this.onDragOver);
405+
document.body.removeEventListener("drop", this.onDrop);
406+
document.body.removeEventListener("mousedown", this.onMouseDown);
407+
document.body.removeEventListener("keydown", this.onKeyDown);
346408
}
347409

348410
addBlock() {

0 commit comments

Comments
 (0)