1
1
import { DOMParser , Slice } from "@tiptap/pm/model" ;
2
- import { EditorState , Plugin , PluginKey , PluginView } from "@tiptap/pm/state" ;
2
+ import {
3
+ EditorState ,
4
+ Plugin ,
5
+ PluginKey ,
6
+ TextSelection ,
7
+ PluginView ,
8
+ } from "@tiptap/pm/state" ;
3
9
import { EditorView } from "@tiptap/pm/view" ;
4
10
5
11
import { Block } from "../../blocks/defaultBlocks.js" ;
@@ -268,21 +274,56 @@ export class SideMenuView<
268
274
}
269
275
} ;
270
276
271
- /**
272
- * If the event is outside the editor contents,
273
- * we dispatch a fake event, so that we can still drop the content
274
- * when dragging / dropping to the side of the editor
275
- */
276
277
onDrop = ( event : DragEvent ) => {
278
+ // Content from outside a BlockNote editor is being dropped - just let
279
+ // ProseMirror's default behaviour handle it.
280
+ if ( this . pmView . dragging === null ) {
281
+ return ;
282
+ }
283
+
277
284
this . editor . _tiptapEditor . commands . blur ( ) ;
278
285
279
- // ProseMirror doesn't remove the dragged content if it's dropped outside
280
- // the editor (e.g. to other editors), so we need to do it manually. Since
281
- // the dragged content is the same as the selected content, we can just
282
- // delete the selection.
283
- if ( this . isDragOrigin && ! this . pmView . dom . contains ( event . target as Node ) ) {
284
- this . pmView . dispatch ( this . pmView . state . tr . deleteSelection ( ) ) ;
286
+ // When ProseMirror handles a drop event on the editor while
287
+ // `view.dragging` is set, it deletes the selected content. However, if
288
+ // a block from a different editor is being dropped, this causes some
289
+ // issues that the code below fixes:
290
+ if ( ! this . isDragOrigin && this . pmView . dom . contains ( event . target as Node ) ) {
291
+ // 1. Because the editor selection is unrelated to the dragged content,
292
+ // we don't want PM to delete its content. Therefore, we collapse the
293
+ // selection.
294
+ this . pmView . dispatch (
295
+ this . pmView . state . tr . setSelection (
296
+ TextSelection . create (
297
+ this . pmView . state . tr . doc ,
298
+ this . pmView . state . tr . selection . to
299
+ )
300
+ )
301
+ ) ;
302
+ } else if (
303
+ this . isDragOrigin &&
304
+ ! this . pmView . dom . contains ( event . target as Node )
305
+ ) {
306
+ // 2. Because the editor from which the block originates doesn't get a
307
+ // drop event on it, PM doesn't delete its selected content. Therefore, we
308
+ // need to do so manually.
309
+ //
310
+ // Note: Deleting the selected content from the editor from which the
311
+ // block originates, may change its height. This can cause the position of
312
+ // the editor in which the block is being dropping to shift, before it
313
+ // can handle the drop event. That in turn can cause the drop to happen
314
+ // somewhere other than the user intended. To get around this, we delay
315
+ // deleting the selected content until all editors have had the chance to
316
+ // handle the event.
317
+ setTimeout (
318
+ ( ) => this . pmView . dispatch ( this . pmView . state . tr . deleteSelection ( ) ) ,
319
+ 0
320
+ ) ;
285
321
}
322
+ // 3. PM only clears `view.dragging` on the editor that the block was
323
+ // dropped, so we manually have to clear it on all the others. However,
324
+ // PM also needs to read `view.dragging` while handling the event, so we
325
+ // use a `setTimeout` to ensure it's only cleared after that.
326
+ setTimeout ( ( ) => ( this . pmView . dragging = null ) , 0 ) ;
286
327
287
328
if (
288
329
this . sideMenuDetection === "editor" ||
@@ -298,6 +339,11 @@ export class SideMenuView<
298
339
} ) ;
299
340
300
341
if ( ! pos || pos . inside === - 1 ) {
342
+ /**
343
+ * When `this.sideMenuSelection === "viewport"`, if the event is outside the
344
+ * editor contents, we dispatch a fake event, so that we can still drop the
345
+ * content when dragging / dropping to the side of the editor
346
+ */
301
347
const evt = this . createSyntheticEvent ( event ) ;
302
348
// console.log("dispatch fake drop");
303
349
this . pmView . dom . dispatchEvent ( evt ) ;
@@ -323,25 +369,27 @@ export class SideMenuView<
323
369
* access `dataTransfer` contents on `dragstart` and `drop` events.
324
370
*/
325
371
onDragStart = ( event : DragEvent ) => {
326
- if ( ! this . pmView . dragging ) {
327
- const html = event . dataTransfer ?. getData ( "blocknote/html" ) ;
328
- if ( ! html ) {
329
- return ;
330
- }
372
+ const html = event . dataTransfer ?. getData ( "blocknote/html" ) ;
373
+ if ( ! html ) {
374
+ return ;
375
+ }
376
+
377
+ if ( this . pmView . dragging ) {
378
+ throw new Error ( "New drag was started while an existing drag is ongoing" ) ;
379
+ }
331
380
332
- const element = document . createElement ( "div" ) ;
333
- element . innerHTML = html ;
381
+ const element = document . createElement ( "div" ) ;
382
+ element . innerHTML = html ;
334
383
335
- const parser = DOMParser . fromSchema ( this . pmView . state . schema ) ;
336
- const node = parser . parse ( element , {
337
- topNode : this . pmView . state . schema . nodes [ "blockGroup" ] . create ( ) ,
338
- } ) ;
384
+ const parser = DOMParser . fromSchema ( this . pmView . state . schema ) ;
385
+ const node = parser . parse ( element , {
386
+ topNode : this . pmView . state . schema . nodes [ "blockGroup" ] . create ( ) ,
387
+ } ) ;
339
388
340
- this . pmView . dragging = {
341
- slice : new Slice ( node . content , 0 , 0 ) ,
342
- move : true ,
343
- } ;
344
- }
389
+ this . pmView . dragging = {
390
+ slice : new Slice ( node . content , 0 , 0 ) ,
391
+ move : true ,
392
+ } ;
345
393
} ;
346
394
347
395
/**
0 commit comments