@@ -150,8 +150,10 @@ function setDragImage(view: EditorView, from: number, to = from) {
150
150
}
151
151
152
152
// dataTransfer.setDragImage(element) only works if element is attached to the DOM.
153
+ unsetDragImage ( ) ;
153
154
dragImageElement = parentClone ;
154
- dragImageElement . className = styles . dragPreview ;
155
+ dragImageElement . className =
156
+ dragImageElement . className + " " + styles . dragPreview ;
155
157
document . body . appendChild ( dragImageElement ) ;
156
158
}
157
159
@@ -245,104 +247,164 @@ export class BlockMenuView {
245
247
246
248
this . blockMenu = blockMenuFactory ( this . getStaticParams ( ) ) ;
247
249
250
+ document . body . addEventListener ( "drop" , this . onDrop , true ) ;
251
+ document . body . addEventListener ( "dragover" , this . onDragOver ) ;
252
+
248
253
// 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 ) ;
308
255
309
256
// Hides and unfreezes the menu whenever the user selects the editor with the mouse or presses a key.
310
257
// 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
+ }
322
261
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
+ } ) ;
334
275
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
+ } ;
340
397
341
398
destroy ( ) {
342
399
if ( this . menuOpen ) {
343
400
this . menuOpen = false ;
344
401
this . blockMenu . hide ( ) ;
345
402
}
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 ) ;
346
408
}
347
409
348
410
addBlock ( ) {
0 commit comments