@blocknote/core 0.8.2 → 0.8.4-alpha.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.
Files changed (72) hide show
  1. package/README.md +4 -0
  2. package/dist/blocknote.js +1777 -1849
  3. package/dist/blocknote.js.map +1 -1
  4. package/dist/blocknote.umd.cjs +4 -4
  5. package/dist/blocknote.umd.cjs.map +1 -1
  6. package/dist/style.css +1 -1
  7. package/package.json +4 -4
  8. package/src/BlockNoteEditor.ts +89 -39
  9. package/src/BlockNoteExtensions.ts +1 -58
  10. package/src/api/formatConversions/__snapshots__/formatConversions.test.ts.snap +10 -10
  11. package/src/api/formatConversions/formatConversions.test.ts +587 -605
  12. package/src/api/nodeConversions/__snapshots__/nodeConversions.test.ts.snap +15 -15
  13. package/src/api/nodeConversions/nodeConversions.test.ts +90 -94
  14. package/src/extensions/Blocks/api/blockTypes.ts +3 -2
  15. package/src/extensions/Blocks/helpers/getBlockInfoFromPos.ts +6 -0
  16. package/src/extensions/FormattingToolbar/FormattingToolbarPlugin.ts +101 -114
  17. package/src/extensions/HyperlinkToolbar/HyperlinkToolbarPlugin.ts +184 -149
  18. package/src/extensions/Placeholder/PlaceholderExtension.ts +2 -2
  19. package/src/extensions/{DraggableBlocks/DraggableBlocksPlugin.ts → SideMenu/SideMenuPlugin.ts} +181 -164
  20. package/src/extensions/SlashMenu/BaseSlashMenuItem.ts +7 -30
  21. package/src/extensions/SlashMenu/SlashMenuPlugin.ts +51 -0
  22. package/src/extensions/SlashMenu/defaultSlashMenuItems.ts +109 -0
  23. package/src/extensions/UniqueID/UniqueID.ts +29 -30
  24. package/src/index.ts +9 -8
  25. package/src/shared/BaseUiElementTypes.ts +8 -0
  26. package/src/shared/EditorElement.ts +0 -16
  27. package/src/shared/EventEmitter.ts +58 -0
  28. package/src/shared/plugins/suggestion/SuggestionItem.ts +3 -6
  29. package/src/shared/plugins/suggestion/SuggestionPlugin.ts +341 -403
  30. package/types/src/BlockNoteEditor.d.ts +18 -11
  31. package/types/src/BlockNoteExtensions.d.ts +0 -19
  32. package/types/src/extensions/Blocks/api/blockTypes.d.ts +3 -2
  33. package/types/src/extensions/FormattingToolbar/FormattingToolbarPlugin.d.ts +18 -24
  34. package/types/src/extensions/HyperlinkToolbar/HyperlinkToolbarPlugin.d.ts +37 -10
  35. package/types/src/extensions/SideMenu/SideMenuPlugin.d.ts +79 -0
  36. package/types/src/extensions/SlashMenu/BaseSlashMenuItem.d.ts +5 -18
  37. package/types/src/extensions/SlashMenu/SlashMenuPlugin.d.ts +13 -0
  38. package/types/src/extensions/SlashMenu/defaultSlashMenuItems.d.ts +1 -69
  39. package/types/src/index.d.ts +9 -8
  40. package/types/src/shared/BaseUiElementTypes.d.ts +7 -0
  41. package/types/src/shared/EditorElement.d.ts +0 -10
  42. package/types/src/shared/EventEmitter.d.ts +11 -0
  43. package/types/src/shared/plugins/suggestion/SuggestionItem.d.ts +2 -7
  44. package/types/src/shared/plugins/suggestion/SuggestionPlugin.d.ts +12 -43
  45. package/src/extensions/DraggableBlocks/BlockSideMenuFactoryTypes.ts +0 -29
  46. package/src/extensions/DraggableBlocks/DraggableBlocksExtension.ts +0 -37
  47. package/src/extensions/FormattingToolbar/FormattingToolbarExtension.ts +0 -37
  48. package/src/extensions/FormattingToolbar/FormattingToolbarFactoryTypes.ts +0 -18
  49. package/src/extensions/HyperlinkToolbar/HyperlinkMark.ts +0 -28
  50. package/src/extensions/HyperlinkToolbar/HyperlinkToolbarFactoryTypes.ts +0 -19
  51. package/src/extensions/SlashMenu/SlashMenuExtension.ts +0 -53
  52. package/src/extensions/SlashMenu/defaultSlashMenuItems.tsx +0 -195
  53. package/src/extensions/SlashMenu/index.ts +0 -5
  54. package/src/shared/plugins/suggestion/SuggestionsMenuFactoryTypes.ts +0 -21
  55. package/types/src/CustomBlock.d.ts +0 -15
  56. package/types/src/extensions/Blocks/nodes/BlockContent/TableContent/TableCol.d.ts +0 -2
  57. package/types/src/extensions/Blocks/nodes/BlockContent/TableContent/TableContent.d.ts +0 -2
  58. package/types/src/extensions/Blocks/nodes/BlockContent/TableContent/TableRow.d.ts +0 -2
  59. package/types/src/extensions/DraggableBlocks/BlockSideMenuFactoryTypes.d.ts +0 -17
  60. package/types/src/extensions/DraggableBlocks/DraggableBlocksExtension.d.ts +0 -16
  61. package/types/src/extensions/DraggableBlocks/DraggableBlocksPlugin.d.ts +0 -49
  62. package/types/src/extensions/FormattingToolbar/FormattingToolbarExtension.d.ts +0 -11
  63. package/types/src/extensions/FormattingToolbar/FormattingToolbarFactoryTypes.d.ts +0 -10
  64. package/types/src/extensions/HyperlinkToolbar/HyperlinkMark.d.ts +0 -8
  65. package/types/src/extensions/HyperlinkToolbar/HyperlinkToolbarFactoryTypes.d.ts +0 -12
  66. package/types/src/extensions/Placeholder/localisation/index.d.ts +0 -2
  67. package/types/src/extensions/Placeholder/localisation/translation.d.ts +0 -51
  68. package/types/src/extensions/SlashMenu/SlashMenuExtension.d.ts +0 -13
  69. package/types/src/extensions/SlashMenu/index.d.ts +0 -4
  70. package/types/src/shared/plugins/suggestion/SuggestionsMenuFactoryTypes.d.ts +0 -12
  71. /package/src/extensions/{DraggableBlocks → SideMenu}/MultipleNodeSelection.ts +0 -0
  72. /package/types/src/extensions/{DraggableBlocks → SideMenu}/MultipleNodeSelection.d.ts +0 -0
@@ -1,27 +1,27 @@
1
- import { Editor } from "@tiptap/core";
1
+ import { PluginView } from "@tiptap/pm/state";
2
2
  import { Node } from "prosemirror-model";
3
3
  import { NodeSelection, Plugin, PluginKey, Selection } from "prosemirror-state";
4
4
  import * as pv from "prosemirror-view";
5
5
  import { EditorView } from "prosemirror-view";
6
+ import { BlockNoteEditor } from "../../BlockNoteEditor";
6
7
  import styles from "../../editor.module.css";
8
+ import { BaseUiElementState } from "../../shared/BaseUiElementTypes";
9
+ import { EventEmitter } from "../../shared/EventEmitter";
10
+ import { Block, BlockSchema } from "../Blocks/api/blockTypes";
7
11
  import { getBlockInfoFromPos } from "../Blocks/helpers/getBlockInfoFromPos";
8
- import { SlashMenuPluginKey } from "../SlashMenu/SlashMenuExtension";
9
- import {
10
- BlockSideMenu,
11
- BlockSideMenuDynamicParams,
12
- BlockSideMenuFactory,
13
- BlockSideMenuStaticParams,
14
- } from "./BlockSideMenuFactoryTypes";
15
- import { DraggableBlocksOptions } from "./DraggableBlocksExtension";
12
+ import { slashMenuPluginKey } from "../SlashMenu/SlashMenuPlugin";
16
13
  import { MultipleNodeSelection } from "./MultipleNodeSelection";
17
- import { BlockNoteEditor } from "../../BlockNoteEditor";
18
- import { BlockSchema } from "../Blocks/api/blockTypes";
19
14
 
20
15
  const serializeForClipboard = (pv as any).__serializeForClipboard;
21
16
  // code based on https://github.com/ueberdosis/tiptap/issues/323#issuecomment-506637799
22
17
 
23
18
  let dragImageElement: Element | undefined;
24
19
 
20
+ export type SideMenuState<BSchema extends BlockSchema> = BaseUiElementState & {
21
+ // The block that the side menu is attached to.
22
+ block: Block<BSchema>;
23
+ };
24
+
25
25
  function getDraggableBlockFromCoords(
26
26
  coords: { left: number; top: number },
27
27
  view: EditorView
@@ -176,7 +176,10 @@ function unsetDragImage() {
176
176
  }
177
177
  }
178
178
 
179
- function dragStart(e: DragEvent, view: EditorView) {
179
+ function dragStart(
180
+ e: { dataTransfer: DataTransfer | null; clientY: number },
181
+ view: EditorView
182
+ ) {
180
183
  if (!e.dataTransfer) {
181
184
  return;
182
185
  }
@@ -224,59 +227,45 @@ function dragStart(e: DragEvent, view: EditorView) {
224
227
  }
225
228
  }
226
229
 
227
- export type BlockMenuViewProps<BSchema extends BlockSchema> = {
228
- tiptapEditor: Editor;
229
- editor: BlockNoteEditor<BSchema>;
230
- blockMenuFactory: BlockSideMenuFactory<BSchema>;
231
- horizontalPosAnchoredAtRoot: boolean;
232
- };
233
-
234
- export class BlockMenuView<BSchema extends BlockSchema> {
235
- editor: BlockNoteEditor<BSchema>;
236
- private ttEditor: Editor;
230
+ export class SideMenuView<BSchema extends BlockSchema> implements PluginView {
231
+ private sideMenuState?: SideMenuState<BSchema>;
237
232
 
238
233
  // When true, the drag handle with be anchored at the same level as root elements
239
234
  // When false, the drag handle with be just to the left of the element
240
- horizontalPosAnchoredAtRoot: boolean;
235
+ // TODO: Is there any case where we want this to be false?
236
+ private horizontalPosAnchoredAtRoot: boolean;
237
+ private horizontalPosAnchor: number;
241
238
 
242
- horizontalPosAnchor: number;
239
+ private hoveredBlock: HTMLElement | undefined;
243
240
 
244
- blockMenu: BlockSideMenu<BSchema>;
241
+ // Used to check if currently dragged content comes from this editor instance.
242
+ public isDragging = false;
245
243
 
246
- hoveredBlock: HTMLElement | undefined;
244
+ public menuFrozen = false;
247
245
 
248
- // Used to check if currently dragged content comes from this editor instance.
249
- isDragging = false;
250
- menuOpen = false;
251
- menuFrozen = false;
252
-
253
- private lastPosition: DOMRect | undefined;
254
-
255
- constructor({
256
- tiptapEditor,
257
- editor,
258
- blockMenuFactory,
259
- horizontalPosAnchoredAtRoot,
260
- }: BlockMenuViewProps<BSchema>) {
261
- this.editor = editor;
262
- this.ttEditor = tiptapEditor;
263
- this.horizontalPosAnchoredAtRoot = horizontalPosAnchoredAtRoot;
246
+ constructor(
247
+ private readonly editor: BlockNoteEditor<BSchema>,
248
+ private readonly pmView: EditorView,
249
+ private readonly updateSideMenu: (
250
+ sideMenuState: SideMenuState<BSchema>
251
+ ) => void
252
+ ) {
253
+ this.horizontalPosAnchoredAtRoot = true;
264
254
  this.horizontalPosAnchor = (
265
- this.ttEditor.view.dom.firstChild! as HTMLElement
255
+ this.pmView.dom.firstChild! as HTMLElement
266
256
  ).getBoundingClientRect().x;
267
257
 
268
- this.blockMenu = blockMenuFactory(this.getStaticParams());
269
-
270
258
  document.body.addEventListener("drop", this.onDrop, true);
271
259
  document.body.addEventListener("dragover", this.onDragOver);
272
- this.ttEditor.view.dom.addEventListener("dragstart", this.onDragStart);
260
+ this.pmView.dom.addEventListener("dragstart", this.onDragStart);
273
261
 
274
262
  // Shows or updates menu position whenever the cursor moves, if the menu isn't frozen.
275
263
  document.body.addEventListener("mousemove", this.onMouseMove, true);
276
264
 
277
- // Hides and unfreezes the menu whenever the user selects the editor with the mouse or presses a key.
278
- // TODO: Better integration with suggestions menu and only editor scope?
279
- document.body.addEventListener("mousedown", this.onMouseDown, true);
265
+ // Makes menu scroll with the page.
266
+ document.addEventListener("scroll", this.onScroll);
267
+
268
+ // Hides and unfreezes the menu whenever the user presses a key.
280
269
  document.body.addEventListener("keydown", this.onKeyDown, true);
281
270
  }
282
271
 
@@ -293,10 +282,13 @@ export class BlockMenuView<BSchema extends BlockSchema> {
293
282
  * when dragging / dropping to the side of the editor
294
283
  */
295
284
  onDrop = (event: DragEvent) => {
285
+ this.editor._tiptapEditor.commands.blur();
286
+
296
287
  if ((event as any).synthetic || !this.isDragging) {
297
288
  return;
298
289
  }
299
- let pos = this.ttEditor.view.posAtCoords({
290
+
291
+ let pos = this.pmView.posAtCoords({
300
292
  left: event.clientX,
301
293
  top: event.clientY,
302
294
  });
@@ -306,7 +298,7 @@ export class BlockMenuView<BSchema extends BlockSchema> {
306
298
  if (!pos || pos.inside === -1) {
307
299
  const evt = new Event("drop", event) as any;
308
300
  const editorBoundingBox = (
309
- this.ttEditor.view.dom.firstChild! as HTMLElement
301
+ this.pmView.dom.firstChild! as HTMLElement
310
302
  ).getBoundingClientRect();
311
303
  evt.clientX = editorBoundingBox.left + editorBoundingBox.width / 2;
312
304
  evt.clientY = event.clientY;
@@ -314,7 +306,7 @@ export class BlockMenuView<BSchema extends BlockSchema> {
314
306
  evt.preventDefault = () => event.preventDefault();
315
307
  evt.synthetic = true; // prevent recursion
316
308
  // console.log("dispatch fake drop");
317
- this.ttEditor.view.dom.dispatchEvent(evt);
309
+ this.pmView.dom.dispatchEvent(evt);
318
310
  }
319
311
  };
320
312
 
@@ -327,7 +319,7 @@ export class BlockMenuView<BSchema extends BlockSchema> {
327
319
  if ((event as any).synthetic || !this.isDragging) {
328
320
  return;
329
321
  }
330
- let pos = this.ttEditor.view.posAtCoords({
322
+ let pos = this.pmView.posAtCoords({
331
323
  left: event.clientX,
332
324
  top: event.clientY,
333
325
  });
@@ -335,7 +327,7 @@ export class BlockMenuView<BSchema extends BlockSchema> {
335
327
  if (!pos || pos.inside === -1) {
336
328
  const evt = new Event("dragover", event) as any;
337
329
  const editorBoundingBox = (
338
- this.ttEditor.view.dom.firstChild! as HTMLElement
330
+ this.pmView.dom.firstChild! as HTMLElement
339
331
  ).getBoundingClientRect();
340
332
  evt.clientX = editorBoundingBox.left + editorBoundingBox.width / 2;
341
333
  evt.clientY = event.clientY;
@@ -343,29 +335,15 @@ export class BlockMenuView<BSchema extends BlockSchema> {
343
335
  evt.preventDefault = () => event.preventDefault();
344
336
  evt.synthetic = true; // prevent recursion
345
337
  // console.log("dispatch fake dragover");
346
- this.ttEditor.view.dom.dispatchEvent(evt);
338
+ this.pmView.dom.dispatchEvent(evt);
347
339
  }
348
340
  };
349
341
 
350
342
  onKeyDown = (_event: KeyboardEvent) => {
351
- if (this.menuOpen) {
352
- this.menuOpen = false;
353
- this.blockMenu.hide();
343
+ if (this.sideMenuState?.show) {
344
+ this.sideMenuState.show = false;
345
+ this.updateSideMenu(this.sideMenuState);
354
346
  }
355
-
356
- this.menuFrozen = false;
357
- };
358
-
359
- onMouseDown = (event: MouseEvent) => {
360
- if (this.blockMenu.element?.contains(event.target as HTMLElement)) {
361
- return;
362
- }
363
-
364
- if (this.menuOpen) {
365
- this.menuOpen = false;
366
- this.blockMenu.hide();
367
- }
368
-
369
347
  this.menuFrozen = false;
370
348
  };
371
349
 
@@ -379,18 +357,19 @@ export class BlockMenuView<BSchema extends BlockSchema> {
379
357
  // blockGroup that wraps all blocks in the editor) for more accurate side
380
358
  // menu placement.
381
359
  const editorBoundingBox = (
382
- this.ttEditor.view.dom.firstChild! as HTMLElement
360
+ this.pmView.dom.firstChild! as HTMLElement
383
361
  ).getBoundingClientRect();
384
362
  // We want the full area of the editor to check if the cursor is hovering
385
363
  // above it though.
386
- const editorOuterBoundingBox =
387
- this.ttEditor.view.dom.getBoundingClientRect();
364
+ const editorOuterBoundingBox = this.pmView.dom.getBoundingClientRect();
388
365
  const cursorWithinEditor =
389
366
  event.clientX >= editorOuterBoundingBox.left &&
390
367
  event.clientX <= editorOuterBoundingBox.right &&
391
368
  event.clientY >= editorOuterBoundingBox.top &&
392
369
  event.clientY <= editorOuterBoundingBox.bottom;
393
370
 
371
+ const editorWrapper = this.pmView.dom.parentElement!;
372
+
394
373
  // Doesn't update if the mouse hovers an element that's over the editor but
395
374
  // isn't a part of it or the side menu.
396
375
  if (
@@ -400,15 +379,14 @@ export class BlockMenuView<BSchema extends BlockSchema> {
400
379
  event &&
401
380
  event.target &&
402
381
  // Element is outside the editor
403
- this.ttEditor.view.dom !== event.target &&
404
- !this.ttEditor.view.dom.contains(event.target as HTMLElement) &&
405
- // Element is outside the side menu
406
- this.blockMenu.element !== event.target &&
407
- !this.blockMenu.element?.contains(event.target as HTMLElement)
382
+ !(
383
+ editorWrapper === event.target ||
384
+ editorWrapper.contains(event.target as HTMLElement)
385
+ )
408
386
  ) {
409
- if (this.menuOpen) {
410
- this.menuOpen = false;
411
- this.blockMenu.hide();
387
+ if (this.sideMenuState?.show) {
388
+ this.sideMenuState.show = false;
389
+ this.updateSideMenu(this.sideMenuState);
412
390
  }
413
391
 
414
392
  return;
@@ -421,13 +399,13 @@ export class BlockMenuView<BSchema extends BlockSchema> {
421
399
  left: editorBoundingBox.left + editorBoundingBox.width / 2, // take middle of editor
422
400
  top: event.clientY,
423
401
  };
424
- const block = getDraggableBlockFromCoords(coords, this.ttEditor.view);
402
+ const block = getDraggableBlockFromCoords(coords, this.pmView);
425
403
 
426
404
  // Closes the menu if the mouse cursor is beyond the editor vertically.
427
405
  if (!block || !this.editor.isEditable) {
428
- if (this.menuOpen) {
429
- this.menuOpen = false;
430
- this.blockMenu.hide();
406
+ if (this.sideMenuState?.show) {
407
+ this.sideMenuState.show = false;
408
+ this.updateSideMenu(this.sideMenuState);
431
409
  }
432
410
 
433
411
  return;
@@ -435,7 +413,7 @@ export class BlockMenuView<BSchema extends BlockSchema> {
435
413
 
436
414
  // Doesn't update if the menu is already open and the mouse cursor is still hovering the same block.
437
415
  if (
438
- this.menuOpen &&
416
+ this.sideMenuState?.show &&
439
417
  this.hoveredBlock?.hasAttribute("data-id") &&
440
418
  this.hoveredBlock?.getAttribute("data-id") === block.id
441
419
  ) {
@@ -453,37 +431,69 @@ export class BlockMenuView<BSchema extends BlockSchema> {
453
431
 
454
432
  // Shows or updates elements.
455
433
  if (this.editor.isEditable) {
456
- if (!this.menuOpen) {
457
- this.menuOpen = true;
458
- this.blockMenu.render(this.getDynamicParams(), true);
459
- } else {
460
- this.blockMenu.render(this.getDynamicParams(), false);
461
- }
434
+ const blockContentBoundingBox = blockContent.getBoundingClientRect();
435
+
436
+ this.sideMenuState = {
437
+ show: true,
438
+ referencePos: new DOMRect(
439
+ this.horizontalPosAnchoredAtRoot
440
+ ? this.horizontalPosAnchor
441
+ : blockContentBoundingBox.x,
442
+ blockContentBoundingBox.y,
443
+ blockContentBoundingBox.width,
444
+ blockContentBoundingBox.height
445
+ ),
446
+ block: this.editor.getBlock(
447
+ this.hoveredBlock!.getAttribute("data-id")!
448
+ )!,
449
+ };
450
+
451
+ this.updateSideMenu(this.sideMenuState);
452
+ }
453
+ };
454
+
455
+ onScroll = () => {
456
+ if (this.sideMenuState?.show) {
457
+ const blockContent = this.hoveredBlock!.firstChild as HTMLElement;
458
+ const blockContentBoundingBox = blockContent.getBoundingClientRect();
459
+
460
+ this.sideMenuState.referencePos = new DOMRect(
461
+ this.horizontalPosAnchoredAtRoot
462
+ ? this.horizontalPosAnchor
463
+ : blockContentBoundingBox.x,
464
+ blockContentBoundingBox.y,
465
+ blockContentBoundingBox.width,
466
+ blockContentBoundingBox.height
467
+ );
468
+ this.updateSideMenu(this.sideMenuState);
462
469
  }
463
470
  };
464
471
 
465
472
  destroy() {
466
- if (this.menuOpen) {
467
- this.menuOpen = false;
468
- this.blockMenu.hide();
473
+ if (this.sideMenuState?.show) {
474
+ this.sideMenuState.show = false;
475
+ this.updateSideMenu(this.sideMenuState);
469
476
  }
470
477
  document.body.removeEventListener("mousemove", this.onMouseMove);
471
478
  document.body.removeEventListener("dragover", this.onDragOver);
472
- this.ttEditor.view.dom.removeEventListener("dragstart", this.onDragStart);
473
- document.body.removeEventListener("drop", this.onDrop);
474
- document.body.removeEventListener("mousedown", this.onMouseDown);
475
- document.body.removeEventListener("keydown", this.onKeyDown);
479
+ this.pmView.dom.removeEventListener("dragstart", this.onDragStart);
480
+ document.body.removeEventListener("drop", this.onDrop, true);
481
+ document.removeEventListener("scroll", this.onScroll);
482
+ document.body.removeEventListener("keydown", this.onKeyDown, true);
476
483
  }
477
484
 
478
485
  addBlock() {
479
- this.menuOpen = false;
486
+ if (this.sideMenuState?.show) {
487
+ this.sideMenuState.show = false;
488
+ this.updateSideMenu(this.sideMenuState);
489
+ }
490
+
480
491
  this.menuFrozen = true;
481
- this.blockMenu.hide();
482
492
 
483
493
  const blockContent = this.hoveredBlock!.firstChild! as HTMLElement;
484
494
  const blockContentBoundingBox = blockContent.getBoundingClientRect();
485
495
 
486
- const pos = this.ttEditor.view.posAtCoords({
496
+ const pos = this.pmView.posAtCoords({
487
497
  left: blockContentBoundingBox.left + blockContentBoundingBox.width / 2,
488
498
  top: blockContentBoundingBox.top + blockContentBoundingBox.height / 2,
489
499
  });
@@ -491,7 +501,10 @@ export class BlockMenuView<BSchema extends BlockSchema> {
491
501
  return;
492
502
  }
493
503
 
494
- const blockInfo = getBlockInfoFromPos(this.ttEditor.state.doc, pos.pos);
504
+ const blockInfo = getBlockInfoFromPos(
505
+ this.editor._tiptapEditor.state.doc,
506
+ pos.pos
507
+ );
495
508
  if (blockInfo === undefined) {
496
509
  return;
497
510
  }
@@ -503,84 +516,88 @@ export class BlockMenuView<BSchema extends BlockSchema> {
503
516
  const newBlockInsertionPos = endPos + 1;
504
517
  const newBlockContentPos = newBlockInsertionPos + 2;
505
518
 
506
- this.ttEditor
519
+ this.editor._tiptapEditor
507
520
  .chain()
508
521
  .BNCreateBlock(newBlockInsertionPos)
509
522
  .BNUpdateBlock(newBlockContentPos, { type: "paragraph", props: {} })
510
523
  .setTextSelection(newBlockContentPos)
511
524
  .run();
512
525
  } else {
513
- this.ttEditor.commands.setTextSelection(endPos);
526
+ this.editor._tiptapEditor.commands.setTextSelection(endPos);
514
527
  }
515
528
 
516
529
  // Focuses and activates the suggestion menu.
517
- this.ttEditor.view.focus();
518
- this.ttEditor.view.dispatch(
519
- this.ttEditor.view.state.tr.scrollIntoView().setMeta(SlashMenuPluginKey, {
530
+ this.pmView.focus();
531
+ this.pmView.dispatch(
532
+ this.pmView.state.tr.scrollIntoView().setMeta(slashMenuPluginKey, {
520
533
  // TODO import suggestion plugin key
521
534
  activate: true,
522
535
  type: "drag",
523
536
  })
524
537
  );
525
538
  }
539
+ }
526
540
 
527
- getStaticParams(): BlockSideMenuStaticParams<BSchema> {
528
- return {
529
- editor: this.editor,
530
- addBlock: () => this.addBlock(),
531
- blockDragStart: (event: DragEvent) => {
532
- // Sets isDragging when dragging blocks.
533
- this.isDragging = true;
534
- dragStart(event, this.ttEditor.view);
535
- },
536
- blockDragEnd: () => unsetDragImage(),
537
- freezeMenu: () => {
538
- this.menuFrozen = true;
539
- },
540
- unfreezeMenu: () => {
541
- this.menuFrozen = false;
542
- },
543
- getReferenceRect: () => {
544
- if (!this.menuOpen) {
545
- if (this.lastPosition === undefined) {
546
- throw new Error(
547
- "Attempted to access block reference rect before rendering block side menu."
548
- );
541
+ export const sideMenuPluginKey = new PluginKey("SideMenuPlugin");
542
+
543
+ export class SideMenuProsemirrorPlugin<
544
+ BSchema extends BlockSchema
545
+ > extends EventEmitter<any> {
546
+ private sideMenuView: SideMenuView<BSchema> | undefined;
547
+ public readonly plugin: Plugin;
548
+
549
+ constructor(private readonly editor: BlockNoteEditor<BSchema>) {
550
+ super();
551
+ this.plugin = new Plugin({
552
+ key: sideMenuPluginKey,
553
+ view: (editorView) => {
554
+ this.sideMenuView = new SideMenuView(
555
+ editor,
556
+ editorView,
557
+ (sideMenuState) => {
558
+ this.emit("update", sideMenuState);
549
559
  }
550
-
551
- return this.lastPosition;
552
- }
553
-
554
- const blockContent = this.hoveredBlock!.firstChild! as HTMLElement;
555
- const blockContentBoundingBox = blockContent.getBoundingClientRect();
556
- if (this.horizontalPosAnchoredAtRoot) {
557
- blockContentBoundingBox.x = this.horizontalPosAnchor;
558
- }
559
- this.lastPosition = blockContentBoundingBox;
560
-
561
- return blockContentBoundingBox;
560
+ );
561
+ return this.sideMenuView;
562
562
  },
563
- };
563
+ });
564
564
  }
565
565
 
566
- getDynamicParams(): BlockSideMenuDynamicParams<BSchema> {
567
- return {
568
- block: this.editor.getBlock(this.hoveredBlock!.getAttribute("data-id")!)!,
569
- };
566
+ public onUpdate(callback: (state: SideMenuState<BSchema>) => void) {
567
+ return this.on("update", callback);
570
568
  }
571
- }
572
569
 
573
- export const createDraggableBlocksPlugin = <BSchema extends BlockSchema>(
574
- options: DraggableBlocksOptions<BSchema>
575
- ) => {
576
- return new Plugin({
577
- key: new PluginKey("DraggableBlocksPlugin"),
578
- view: () =>
579
- new BlockMenuView({
580
- tiptapEditor: options.tiptapEditor,
581
- editor: options.editor,
582
- blockMenuFactory: options.blockSideMenuFactory,
583
- horizontalPosAnchoredAtRoot: true,
584
- }),
585
- });
586
- };
570
+ /**
571
+ * If the block is empty, opens the slash menu. If the block has content,
572
+ * creates a new block below and opens the slash menu in it.
573
+ */
574
+ addBlock = () => this.sideMenuView!.addBlock();
575
+
576
+ /**
577
+ * Handles drag & drop events for blocks.
578
+ */
579
+ blockDragStart = (event: {
580
+ dataTransfer: DataTransfer | null;
581
+ clientY: number;
582
+ }) => {
583
+ this.sideMenuView!.isDragging = true;
584
+ dragStart(event, this.editor.prosemirrorView);
585
+ };
586
+
587
+ /**
588
+ * Handles drag & drop events for blocks.
589
+ */
590
+ blockDragEnd = () => unsetDragImage();
591
+ /**
592
+ * Freezes the side menu. When frozen, the side menu will stay
593
+ * attached to the same block regardless of which block is hovered by the
594
+ * mouse cursor.
595
+ */
596
+ freezeMenu = () => (this.sideMenuView!.menuFrozen = true);
597
+ /**
598
+ * Unfreezes the side menu. When frozen, the side menu will stay
599
+ * attached to the same block regardless of which block is hovered by the
600
+ * mouse cursor.
601
+ */
602
+ unfreezeMenu = () => (this.sideMenuView!.menuFrozen = false);
603
+ }
@@ -1,34 +1,11 @@
1
1
  import { SuggestionItem } from "../../shared/plugins/suggestion/SuggestionItem";
2
2
  import { BlockNoteEditor } from "../../BlockNoteEditor";
3
3
  import { BlockSchema } from "../Blocks/api/blockTypes";
4
+ import { DefaultBlockSchema } from "../Blocks/api/defaultBlocks";
4
5
 
5
- /**
6
- * A class that defines a slash command (/<command>).
7
- *
8
- * (Not to be confused with ProseMirror commands nor TipTap commands.)
9
- */
10
- export class BaseSlashMenuItem<
11
- BSchema extends BlockSchema
12
- > extends SuggestionItem {
13
- /**
14
- * Constructs a new slash-command.
15
- *
16
- * @param name The name of the command
17
- * @param execute The callback for creating a new node
18
- * @param aliases Aliases for this command
19
- */
20
- constructor(
21
- public readonly name: string,
22
- public readonly execute: (editor: BlockNoteEditor<BSchema>) => void,
23
- public readonly aliases: string[] = []
24
- ) {
25
- super(name, (query: string): boolean => {
26
- return (
27
- this.name.toLowerCase().startsWith(query.toLowerCase()) ||
28
- this.aliases.filter((alias) =>
29
- alias.toLowerCase().startsWith(query.toLowerCase())
30
- ).length !== 0
31
- );
32
- });
33
- }
34
- }
6
+ export type BaseSlashMenuItem<
7
+ BSchema extends BlockSchema = DefaultBlockSchema
8
+ > = SuggestionItem & {
9
+ execute: (editor: BlockNoteEditor<BSchema>) => void;
10
+ aliases?: string[];
11
+ };
@@ -0,0 +1,51 @@
1
+ import { Plugin, PluginKey } from "prosemirror-state";
2
+
3
+ import { BlockNoteEditor } from "../../BlockNoteEditor";
4
+ import { EventEmitter } from "../../shared/EventEmitter";
5
+ import {
6
+ SuggestionsMenuState,
7
+ setupSuggestionsMenu,
8
+ } from "../../shared/plugins/suggestion/SuggestionPlugin";
9
+ import { BlockSchema } from "../Blocks/api/blockTypes";
10
+ import { BaseSlashMenuItem } from "./BaseSlashMenuItem";
11
+
12
+ export const slashMenuPluginKey = new PluginKey("SlashMenuPlugin");
13
+
14
+ export class SlashMenuProsemirrorPlugin<
15
+ BSchema extends BlockSchema,
16
+ SlashMenuItem extends BaseSlashMenuItem<BSchema>
17
+ > extends EventEmitter<any> {
18
+ public readonly plugin: Plugin;
19
+ public readonly itemCallback: (item: SlashMenuItem) => void;
20
+
21
+ constructor(editor: BlockNoteEditor<BSchema>, items: SlashMenuItem[]) {
22
+ super();
23
+ const suggestions = setupSuggestionsMenu<SlashMenuItem, BSchema>(
24
+ editor,
25
+ (state) => {
26
+ this.emit("update", state);
27
+ },
28
+ slashMenuPluginKey,
29
+ "/",
30
+ (query) =>
31
+ items.filter(
32
+ ({ name, aliases }: SlashMenuItem) =>
33
+ name.toLowerCase().startsWith(query.toLowerCase()) ||
34
+ (aliases &&
35
+ aliases.filter((alias) =>
36
+ alias.toLowerCase().startsWith(query.toLowerCase())
37
+ ).length !== 0)
38
+ ),
39
+ ({ item, editor }) => item.execute(editor)
40
+ );
41
+
42
+ this.plugin = suggestions.plugin;
43
+ this.itemCallback = suggestions.itemCallback;
44
+ }
45
+
46
+ public onUpdate(
47
+ callback: (state: SuggestionsMenuState<SlashMenuItem>) => void
48
+ ) {
49
+ return this.on("update", callback);
50
+ }
51
+ }