@blocknote/core 0.39.1 → 0.41.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 (102) hide show
  1. package/dist/BlockNoteSchema-COA0fsXW.cjs +11 -0
  2. package/dist/BlockNoteSchema-COA0fsXW.cjs.map +1 -0
  3. package/dist/{BlockNoteSchema-oR047ACf.js → BlockNoteSchema-CYRHak18.js} +681 -581
  4. package/dist/BlockNoteSchema-CYRHak18.js.map +1 -0
  5. package/dist/blocknote.cjs +4 -4
  6. package/dist/blocknote.cjs.map +1 -1
  7. package/dist/blocknote.js +3663 -2816
  8. package/dist/blocknote.js.map +1 -1
  9. package/dist/blocks.cjs +1 -1
  10. package/dist/blocks.js +51 -49
  11. package/dist/en-Cl87Uuyf.cjs +2 -0
  12. package/dist/en-Cl87Uuyf.cjs.map +1 -0
  13. package/dist/{en-Bq3Es3Np.js → en-njEqD7AG.js} +9 -3
  14. package/dist/en-njEqD7AG.js.map +1 -0
  15. package/dist/locales.cjs +1 -1
  16. package/dist/locales.cjs.map +1 -1
  17. package/dist/locales.js +122 -2
  18. package/dist/locales.js.map +1 -1
  19. package/dist/style.css +1 -1
  20. package/dist/tsconfig.tsbuildinfo +1 -1
  21. package/dist/webpack-stats.json +1 -1
  22. package/package.json +4 -3
  23. package/src/api/exporters/html/externalHTMLExporter.ts +1 -1
  24. package/src/api/exporters/html/util/serializeBlocksInternalHTML.ts +2 -1
  25. package/src/api/exporters/markdown/markdownExporter.ts +3 -1
  26. package/src/api/exporters/markdown/util/convertVideoToMarkdownRehypePlugin.ts +19 -0
  27. package/src/api/parsers/markdown/parseMarkdown.ts +31 -0
  28. package/src/blocks/Code/block.ts +14 -14
  29. package/src/blocks/Divider/block.ts +49 -0
  30. package/src/blocks/ListItem/BulletListItem/block.ts +9 -1
  31. package/src/blocks/ListItem/CheckListItem/block.ts +1 -0
  32. package/src/blocks/ListItem/NumberedListItem/block.ts +9 -1
  33. package/src/blocks/Table/block.ts +56 -1
  34. package/src/blocks/ToggleWrapper/createToggleWrapper.ts +1 -1
  35. package/src/blocks/defaultBlocks.ts +16 -14
  36. package/src/blocks/index.ts +1 -0
  37. package/src/editor/Block.css +14 -20
  38. package/src/editor/BlockNoteEditor.test.ts +40 -0
  39. package/src/editor/BlockNoteEditor.ts +257 -465
  40. package/src/editor/BlockNoteExtensions.ts +3 -1
  41. package/src/editor/managers/BlockManager.ts +251 -0
  42. package/src/editor/managers/CollaborationManager.ts +212 -0
  43. package/src/editor/managers/EventManager.ts +134 -0
  44. package/src/editor/managers/ExportManager.ts +137 -0
  45. package/src/editor/managers/ExtensionManager.ts +130 -0
  46. package/src/editor/managers/SelectionManager.ts +114 -0
  47. package/src/editor/managers/StateManager.ts +238 -0
  48. package/src/editor/managers/StyleManager.ts +182 -0
  49. package/src/editor/managers/index.ts +11 -0
  50. package/src/extensions/Comments/CommentsPlugin.ts +7 -4
  51. package/src/extensions/SuggestionMenu/getDefaultSlashMenuItems.ts +10 -0
  52. package/src/i18n/locales/ar.ts +6 -0
  53. package/src/i18n/locales/de.ts +6 -0
  54. package/src/i18n/locales/en.ts +6 -0
  55. package/src/i18n/locales/es.ts +6 -0
  56. package/src/i18n/locales/fr.ts +6 -0
  57. package/src/i18n/locales/he.ts +6 -0
  58. package/src/i18n/locales/hr.ts +6 -0
  59. package/src/i18n/locales/is.ts +6 -0
  60. package/src/i18n/locales/it.ts +6 -0
  61. package/src/i18n/locales/ja.ts +6 -0
  62. package/src/i18n/locales/ko.ts +6 -0
  63. package/src/i18n/locales/nl.ts +6 -0
  64. package/src/i18n/locales/no.ts +6 -0
  65. package/src/i18n/locales/pl.ts +6 -0
  66. package/src/i18n/locales/pt.ts +6 -0
  67. package/src/i18n/locales/ru.ts +6 -0
  68. package/src/i18n/locales/sk.ts +6 -0
  69. package/src/i18n/locales/uk.ts +6 -0
  70. package/src/i18n/locales/vi.ts +6 -0
  71. package/src/i18n/locales/zh-tw.ts +6 -0
  72. package/src/i18n/locales/zh.ts +6 -0
  73. package/src/schema/blocks/createSpec.ts +1 -0
  74. package/src/util/string.ts +21 -0
  75. package/types/src/api/exporters/markdown/util/convertVideoToMarkdownRehypePlugin.d.ts +2 -0
  76. package/types/src/blocks/Divider/block.d.ts +3 -0
  77. package/types/src/blocks/Heading/block.d.ts +3 -3
  78. package/types/src/blocks/defaultBlocks.d.ts +2 -1
  79. package/types/src/blocks/index.d.ts +1 -0
  80. package/types/src/editor/BlockNoteEditor.d.ts +75 -48
  81. package/types/src/editor/BlockNoteExtensions.d.ts +2 -1
  82. package/types/src/editor/managers/BlockManager.d.ts +114 -0
  83. package/types/src/editor/managers/CollaborationManager.d.ts +115 -0
  84. package/types/src/editor/managers/EventManager.d.ts +58 -0
  85. package/types/src/editor/managers/ExportManager.d.ts +64 -0
  86. package/types/src/editor/managers/ExtensionManager.d.ts +68 -0
  87. package/types/src/editor/managers/SelectionManager.d.ts +54 -0
  88. package/types/src/editor/managers/StateManager.d.ts +115 -0
  89. package/types/src/editor/managers/StyleManager.d.ts +48 -0
  90. package/types/src/editor/managers/index.d.ts +8 -0
  91. package/types/src/extensions/Comments/CommentsPlugin.d.ts +4 -3
  92. package/types/src/i18n/locales/en.d.ts +6 -0
  93. package/types/src/i18n/locales/sk.d.ts +6 -0
  94. package/types/src/util/string.d.ts +1 -0
  95. package/dist/BlockNoteSchema-DmZ6UQfY.cjs +0 -11
  96. package/dist/BlockNoteSchema-DmZ6UQfY.cjs.map +0 -1
  97. package/dist/BlockNoteSchema-oR047ACf.js.map +0 -1
  98. package/dist/en-Bq3Es3Np.js.map +0 -1
  99. package/dist/en-D3B48eJ7.cjs +0 -2
  100. package/dist/en-D3B48eJ7.cjs.map +0 -1
  101. /package/src/api/exporters/markdown/{removeUnderlinesRehypePlugin.ts → util/removeUnderlinesRehypePlugin.ts} +0 -0
  102. /package/types/src/api/exporters/markdown/{removeUnderlinesRehypePlugin.d.ts → util/removeUnderlinesRehypePlugin.d.ts} +0 -0
@@ -3,93 +3,41 @@ import {
3
3
  createDocument,
4
4
  EditorOptions,
5
5
  Extension,
6
+ FocusPosition,
6
7
  getSchema,
7
8
  InputRule,
8
- isNodeSelection,
9
9
  Mark,
10
- posToDOMRect,
11
10
  Editor as TiptapEditor,
12
11
  Node as TipTapNode,
13
12
  } from "@tiptap/core";
14
- import { redo, undo } from "@tiptap/pm/history";
15
- import {
16
- TextSelection,
17
- type Command,
18
- type Plugin,
19
- type Transaction,
20
- } from "@tiptap/pm/state";
13
+ import { type Command, type Plugin, type Transaction } from "@tiptap/pm/state";
21
14
  import { dropCursor } from "prosemirror-dropcursor";
22
15
  import { Node, Schema } from "prosemirror-model";
23
- import { redoCommand, undoCommand, ySyncPluginKey } from "y-prosemirror";
24
16
  import * as Y from "yjs";
25
17
 
26
- import { insertBlocks } from "../api/blockManipulation/commands/insertBlocks/insertBlocks.js";
27
- import {
28
- moveBlocksDown,
29
- moveBlocksUp,
30
- } from "../api/blockManipulation/commands/moveBlocks/moveBlocks.js";
31
- import {
32
- canNestBlock,
33
- canUnnestBlock,
34
- nestBlock,
35
- unnestBlock,
36
- } from "../api/blockManipulation/commands/nestBlock/nestBlock.js";
37
- import { removeAndInsertBlocks } from "../api/blockManipulation/commands/replaceBlocks/replaceBlocks.js";
38
- import {
39
- updateBlock,
40
- updateBlockTr,
41
- } from "../api/blockManipulation/commands/updateBlock/updateBlock.js";
42
- import {
43
- getBlock,
44
- getNextBlock,
45
- getParentBlock,
46
- getPrevBlock,
47
- } from "../api/blockManipulation/getBlock/getBlock.js";
48
- import { insertContentAt } from "../api/blockManipulation/insertContentAt.js";
49
- import {
50
- getSelection,
51
- getSelectionCutBlocks,
52
- setSelection,
53
- } from "../api/blockManipulation/selections/selection.js";
54
- import {
55
- getTextCursorPosition,
56
- setTextCursorPosition,
57
- } from "../api/blockManipulation/selections/textCursorPosition.js";
58
- import { createExternalHTMLExporter } from "../api/exporters/html/externalHTMLExporter.js";
59
- import { createInternalHTMLSerializer } from "../api/exporters/html/internalHTMLSerializer.js";
60
- import { blocksToMarkdown } from "../api/exporters/markdown/markdownExporter.js";
61
- import { getBlockInfoFromTransaction } from "../api/getBlockInfoFromPos.js";
62
- import {
63
- BlocksChanged,
64
- getBlocksChangedByTransaction,
65
- } from "../api/getBlocksChangedByTransaction.js";
66
- import {
67
- blockToNode,
68
- inlineContentToNodes,
69
- } from "../api/nodeConversions/blockToNode.js";
70
- import { docToBlocks } from "../api/nodeConversions/nodeToBlock.js";
71
- import { HTMLToBlocks } from "../api/parsers/html/parseHTML.js";
72
- import {
73
- markdownToBlocks,
74
- markdownToHTML,
75
- } from "../api/parsers/markdown/parseMarkdown.js";
18
+ import type { BlocksChanged } from "../api/getBlocksChangedByTransaction.js";
76
19
  import { editorHasBlockWithType } from "../blocks/defaultBlockTypeGuards.js";
20
+ import {
21
+ Block,
22
+ BlockNoteSchema,
23
+ DefaultBlockSchema,
24
+ DefaultInlineContentSchema,
25
+ DefaultStyleSchema,
26
+ PartialBlock,
27
+ } from "../blocks/index.js";
77
28
  import type { ThreadStore, User } from "../comments/index.js";
78
- import { BlockChangePlugin } from "../extensions/BlockChange/BlockChangePlugin.js";
79
- import type { CursorPlugin } from "../extensions/Collaboration/CursorPlugin.js";
80
- import type { ForkYDocPlugin } from "../extensions/Collaboration/ForkYDocPlugin.js";
81
29
  import type { CommentsPlugin } from "../extensions/Comments/CommentsPlugin.js";
82
- import { FilePanelProsemirrorPlugin } from "../extensions/FilePanel/FilePanelPlugin.js";
83
- import { FormattingToolbarProsemirrorPlugin } from "../extensions/FormattingToolbar/FormattingToolbarPlugin.js";
84
- import { LinkToolbarProsemirrorPlugin } from "../extensions/LinkToolbar/LinkToolbarPlugin.js";
85
- import { ShowSelectionPlugin } from "../extensions/ShowSelection/ShowSelectionPlugin.js";
86
- import { SideMenuProsemirrorPlugin } from "../extensions/SideMenu/SideMenuPlugin.js";
87
- import { SuggestionMenuProseMirrorPlugin } from "../extensions/SuggestionMenu/SuggestionPlugin.js";
88
- import { TableHandlesProsemirrorPlugin } from "../extensions/TableHandles/TableHandlesPlugin.js";
30
+ import type { FilePanelProsemirrorPlugin } from "../extensions/FilePanel/FilePanelPlugin.js";
31
+ import type { FormattingToolbarProsemirrorPlugin } from "../extensions/FormattingToolbar/FormattingToolbarPlugin.js";
32
+ import type { LinkToolbarProsemirrorPlugin } from "../extensions/LinkToolbar/LinkToolbarPlugin.js";
33
+ import type { ShowSelectionPlugin } from "../extensions/ShowSelection/ShowSelectionPlugin.js";
34
+ import type { SideMenuProsemirrorPlugin } from "../extensions/SideMenu/SideMenuPlugin.js";
35
+ import type { SuggestionMenuProseMirrorPlugin } from "../extensions/SuggestionMenu/SuggestionPlugin.js";
36
+ import type { TableHandlesProsemirrorPlugin } from "../extensions/TableHandles/TableHandlesPlugin.js";
89
37
  import { UniqueID } from "../extensions/UniqueID/UniqueID.js";
90
- import { Dictionary } from "../i18n/dictionary.js";
38
+ import type { Dictionary } from "../i18n/dictionary.js";
91
39
  import { en } from "../i18n/locales/index.js";
92
- import {
40
+ import type {
93
41
  BlockIdentifier,
94
42
  BlockNoteDOMAttributes,
95
43
  BlockSchema,
@@ -102,26 +50,29 @@ import {
102
50
  StyleSchema,
103
51
  StyleSpecs,
104
52
  } from "../schema/index.js";
105
- import "../style.css";
106
53
  import { mergeCSSClasses } from "../util/browser.js";
107
54
  import { EventEmitter } from "../util/EventEmitter.js";
108
- import { NoInfer, UnreachableCaseError } from "../util/typescript.js";
55
+ import type { NoInfer } from "../util/typescript.js";
109
56
  import { BlockNoteExtension } from "./BlockNoteExtension.js";
110
57
  import { getBlockNoteExtensions } from "./BlockNoteExtensions.js";
111
- import { TextCursorPosition } from "./cursorPositionTypes.js";
112
- import { Selection } from "./selectionTypes.js";
113
- import { transformPasted } from "./transformPasted.js";
114
-
115
- // TODO eventually we will want to de-couple this from the editor instance, for now it provides a default schema to use
58
+ import type { TextCursorPosition } from "./cursorPositionTypes.js";
116
59
  import {
117
- Block,
118
- BlockNoteSchema,
119
- DefaultBlockSchema,
120
- DefaultInlineContentSchema,
121
- DefaultStyleSchema,
122
- PartialBlock,
123
- } from "../blocks/index.js";
60
+ BlockManager,
61
+ CollaborationManager,
62
+ type CollaborationOptions,
63
+ EventManager,
64
+ ExportManager,
65
+ ExtensionManager,
66
+ SelectionManager,
67
+ StateManager,
68
+ StyleManager,
69
+ } from "./managers/index.js";
70
+ import type { Selection } from "./selectionTypes.js";
71
+ import { transformPasted } from "./transformPasted.js";
124
72
 
73
+ import { updateBlockTr } from "../api/blockManipulation/commands/updateBlock/updateBlock.js";
74
+ import { getBlockInfoFromTransaction } from "../api/getBlockInfoFromPos.js";
75
+ import { blockToNode } from "../api/nodeConversions/blockToNode.js";
125
76
  import "../style.css";
126
77
 
127
78
  /**
@@ -156,6 +107,13 @@ export type BlockNoteEditorOptions<
156
107
  */
157
108
  animations?: boolean;
158
109
 
110
+ /**
111
+ * Whether the editor should be focused automatically when it's created.
112
+ *
113
+ * @default false
114
+ */
115
+ autofocus?: FocusPosition;
116
+
159
117
  /**
160
118
  * When enabled, allows for collaboration between multiple users.
161
119
  * See [Real-time Collaboration](https://www.blocknotejs.org/docs/advanced/real-time-collaboration) for more info.
@@ -489,41 +447,62 @@ export class BlockNoteEditor<
489
447
  /**
490
448
  * The schema of the editor. The schema defines which Blocks, InlineContent, and Styles are available in the editor.
491
449
  */
492
- public readonly schema: CustomBlockNoteSchema<BSchema, ISchema, SSchema>;
450
+ public readonly schema: BlockNoteSchema<BSchema, ISchema, SSchema>;
493
451
 
494
452
  public readonly blockImplementations: BlockSpecs;
495
453
  public readonly inlineContentImplementations: InlineContentSpecs;
496
454
  public readonly styleImplementations: StyleSpecs;
497
455
 
498
- public readonly formattingToolbar: FormattingToolbarProsemirrorPlugin;
499
- public readonly linkToolbar: LinkToolbarProsemirrorPlugin<
500
- BSchema,
501
- ISchema,
502
- SSchema
503
- >;
504
- public readonly sideMenu: SideMenuProsemirrorPlugin<
456
+ public get formattingToolbar(): FormattingToolbarProsemirrorPlugin {
457
+ return this._extensionManager.formattingToolbar;
458
+ }
459
+
460
+ public get linkToolbar(): LinkToolbarProsemirrorPlugin<
505
461
  BSchema,
506
462
  ISchema,
507
463
  SSchema
508
- >;
509
- public readonly suggestionMenus: SuggestionMenuProseMirrorPlugin<
464
+ > {
465
+ return this._extensionManager.linkToolbar;
466
+ }
467
+
468
+ public get sideMenu(): SideMenuProsemirrorPlugin<BSchema, ISchema, SSchema> {
469
+ return this._extensionManager.sideMenu;
470
+ }
471
+
472
+ public get suggestionMenus(): SuggestionMenuProseMirrorPlugin<
510
473
  BSchema,
511
474
  ISchema,
512
475
  SSchema
513
- >;
514
- public readonly filePanel?: FilePanelProsemirrorPlugin<ISchema, SSchema>;
515
- public readonly tableHandles?: TableHandlesProsemirrorPlugin<
516
- ISchema,
517
- SSchema
518
- >;
519
- public readonly comments?: CommentsPlugin;
476
+ > {
477
+ return this._extensionManager.suggestionMenus;
478
+ }
479
+
480
+ public get filePanel():
481
+ | FilePanelProsemirrorPlugin<ISchema, SSchema>
482
+ | undefined {
483
+ return this._extensionManager.filePanel;
484
+ }
520
485
 
521
- private readonly showSelectionPlugin: ShowSelectionPlugin;
486
+ public get tableHandles():
487
+ | TableHandlesProsemirrorPlugin<ISchema, SSchema>
488
+ | undefined {
489
+ return this._extensionManager.tableHandles;
490
+ }
491
+
492
+ public get comments(): CommentsPlugin | undefined {
493
+ return this._collaborationManager?.comments;
494
+ }
495
+
496
+ public get showSelectionPlugin(): ShowSelectionPlugin {
497
+ return this._extensionManager.showSelectionPlugin;
498
+ }
522
499
 
523
500
  /**
524
501
  * The plugin for forking a document, only defined if in collaboration mode
525
502
  */
526
- public readonly forkYDocPlugin?: ForkYDocPlugin;
503
+ public get forkYDocPlugin() {
504
+ return this._collaborationManager?.forkYDocPlugin;
505
+ }
527
506
  /**
528
507
  * The `uploadFile` method is what the editor uses when files need to be uploaded (for example when selecting an image to upload).
529
508
  * This method should set when creating the editor as this is application-specific.
@@ -627,35 +606,59 @@ export class BlockNoteEditor<
627
606
  },
628
607
  };
629
608
 
609
+ // Initialize CollaborationManager if collaboration is enabled or if comments are configured
610
+ if (newOptions.collaboration || newOptions.comments) {
611
+ const collaborationOptions: CollaborationOptions = {
612
+ // Use collaboration options if available, otherwise provide defaults
613
+ fragment: newOptions.collaboration?.fragment || new Y.XmlFragment(),
614
+ user: newOptions.collaboration?.user || {
615
+ name: "User",
616
+ color: "#FF0000",
617
+ },
618
+ provider: newOptions.collaboration?.provider || null,
619
+ renderCursor: newOptions.collaboration?.renderCursor,
620
+ showCursorLabels: newOptions.collaboration?.showCursorLabels,
621
+ comments: newOptions.comments,
622
+ resolveUsers: newOptions.resolveUsers,
623
+ };
624
+ this._collaborationManager = new CollaborationManager(
625
+ this as any,
626
+ collaborationOptions,
627
+ );
628
+ } else {
629
+ this._collaborationManager = undefined;
630
+ }
631
+
630
632
  if (newOptions.comments && !newOptions.resolveUsers) {
631
633
  throw new Error("resolveUsers is required when using comments");
632
634
  }
633
635
 
634
- this.resolveUsers = newOptions.resolveUsers;
635
-
636
+ // @ts-ignore
636
637
  this.schema = newOptions.schema;
637
638
  this.blockImplementations = newOptions.schema.blockSpecs;
638
639
  this.inlineContentImplementations = newOptions.schema.inlineContentSpecs;
639
640
  this.styleImplementations = newOptions.schema.styleSpecs;
640
641
 
641
- this.extensions = getBlockNoteExtensions({
642
- editor: this,
643
- domAttributes: newOptions.domAttributes || {},
644
- blockSpecs: this.schema.blockSpecs,
645
- styleSpecs: this.schema.styleSpecs,
646
- inlineContentSpecs: this.schema.inlineContentSpecs,
647
- collaboration: newOptions.collaboration,
648
- trailingBlock: newOptions.trailingBlock,
649
- disableExtensions: newOptions.disableExtensions,
650
- setIdAttribute: newOptions.setIdAttribute,
651
- animations: newOptions.animations ?? true,
652
- tableHandles: editorHasBlockWithType(this, "table"),
653
- dropCursor: this.options.dropCursor ?? dropCursor,
654
- placeholders: newOptions.placeholders,
655
- tabBehavior: newOptions.tabBehavior,
656
- comments: newOptions.comments,
657
- pasteHandler: newOptions.pasteHandler,
658
- });
642
+ this.extensions = {
643
+ ...getBlockNoteExtensions({
644
+ editor: this,
645
+ domAttributes: newOptions.domAttributes || {},
646
+ blockSpecs: this.schema.blockSpecs,
647
+ styleSpecs: this.schema.styleSpecs,
648
+ inlineContentSpecs: this.schema.inlineContentSpecs,
649
+ collaboration: newOptions.collaboration,
650
+ trailingBlock: newOptions.trailingBlock,
651
+ disableExtensions: newOptions.disableExtensions,
652
+ setIdAttribute: newOptions.setIdAttribute,
653
+ animations: newOptions.animations ?? true,
654
+ tableHandles: editorHasBlockWithType(this, "table"),
655
+ dropCursor: this.options.dropCursor ?? dropCursor,
656
+ placeholders: newOptions.placeholders,
657
+ tabBehavior: newOptions.tabBehavior,
658
+ pasteHandler: newOptions.pasteHandler,
659
+ }),
660
+ ...this._collaborationManager?.initExtensions(),
661
+ } as any;
659
662
 
660
663
  // add extensions from _tiptapOptions
661
664
  (newOptions._tiptapOptions?.extensions || []).forEach((ext) => {
@@ -708,16 +711,6 @@ export class BlockNoteEditor<
708
711
  })();
709
712
  });
710
713
 
711
- this.formattingToolbar = this.extensions["formattingToolbar"] as any;
712
- this.linkToolbar = this.extensions["linkToolbar"] as any;
713
- this.sideMenu = this.extensions["sideMenu"] as any;
714
- this.suggestionMenus = this.extensions["suggestionMenus"] as any;
715
- this.filePanel = this.extensions["filePanel"] as any;
716
- this.tableHandles = this.extensions["tableHandles"] as any;
717
- this.comments = this.extensions["comments"] as any;
718
- this.showSelectionPlugin = this.extensions["showSelection"] as any;
719
- this.forkYDocPlugin = this.extensions["forkYDocPlugin"] as any;
720
-
721
714
  if (newOptions.uploadFile) {
722
715
  const uploadFile = newOptions.uploadFile;
723
716
  this.uploadFile = async (file, blockId) => {
@@ -850,6 +843,7 @@ export class BlockNoteEditor<
850
843
  ...blockNoteTipTapOptions,
851
844
  ...newOptions._tiptapOptions,
852
845
  element: null,
846
+ autofocus: newOptions.autofocus ?? false,
853
847
  extensions: tiptapExtensions,
854
848
  editorProps: {
855
849
  ...newOptions._tiptapOptions?.editorProps,
@@ -924,13 +918,37 @@ export class BlockNoteEditor<
924
918
  }
925
919
 
926
920
  this.pmSchema.cached.blockNoteEditor = this;
921
+
922
+ // Initialize managers
923
+ this._blockManager = new BlockManager(this as any);
924
+
925
+ this._eventManager = new EventManager(this as any);
926
+ this._exportManager = new ExportManager(this as any);
927
+ this._extensionManager = new ExtensionManager(this as any);
928
+ this._selectionManager = new SelectionManager(this as any);
929
+ this._stateManager = new StateManager(
930
+ this as any,
931
+ collaborationEnabled
932
+ ? {
933
+ undo: this._collaborationManager?.getUndoCommand(),
934
+ redo: this._collaborationManager?.getRedoCommand(),
935
+ }
936
+ : undefined,
937
+ );
938
+ this._styleManager = new StyleManager(this as any);
939
+
927
940
  this.emit("create");
928
941
  }
929
942
 
930
- /**
931
- * Stores the currently active transaction, which is the accumulated transaction from all {@link dispatch} calls during a {@link transact} calls
932
- */
933
- private activeTransaction: Transaction | null = null;
943
+ // Manager instances
944
+ private readonly _blockManager: BlockManager<any, any, any>;
945
+ private readonly _collaborationManager?: CollaborationManager;
946
+ private readonly _eventManager: EventManager<any>;
947
+ private readonly _exportManager: ExportManager<any, any, any>;
948
+ private readonly _extensionManager: ExtensionManager;
949
+ private readonly _selectionManager: SelectionManager<any, any, any>;
950
+ private readonly _stateManager: StateManager;
951
+ private readonly _styleManager: StyleManager<any, any, any>;
934
952
 
935
953
  /**
936
954
  * Execute a prosemirror command. This is mostly for backwards compatibility with older code.
@@ -945,16 +963,7 @@ export class BlockNoteEditor<
945
963
  * ```
946
964
  */
947
965
  public exec(command: Command) {
948
- if (this.activeTransaction) {
949
- throw new Error(
950
- "`exec` should not be called within a `transact` call, move the `exec` call outside of the `transact` call",
951
- );
952
- }
953
- const state = this._tiptapEditor.state;
954
- const view = this._tiptapEditor.view;
955
- const dispatch = (tr: Transaction) => view.dispatch(tr);
956
-
957
- return command(state, dispatch, view);
966
+ return this._stateManager.exec(command);
958
967
  }
959
968
 
960
969
  /**
@@ -970,15 +979,7 @@ export class BlockNoteEditor<
970
979
  * ```
971
980
  */
972
981
  public canExec(command: Command): boolean {
973
- if (this.activeTransaction) {
974
- throw new Error(
975
- "`canExec` should not be called within a `transact` call, move the `canExec` call outside of the `transact` call",
976
- );
977
- }
978
- const state = this._tiptapEditor.state;
979
- const view = this._tiptapEditor.view;
980
-
981
- return command(state, undefined, view);
982
+ return this._stateManager.canExec(command);
982
983
  }
983
984
 
984
985
  /**
@@ -1009,40 +1010,7 @@ export class BlockNoteEditor<
1009
1010
  tr: Transaction,
1010
1011
  ) => T,
1011
1012
  ): T {
1012
- if (this.activeTransaction) {
1013
- // Already in a transaction, so we can just callback immediately
1014
- return callback(this.activeTransaction);
1015
- }
1016
-
1017
- try {
1018
- // Enter transaction mode, by setting a starting transaction
1019
- this.activeTransaction = this._tiptapEditor.state.tr;
1020
-
1021
- // Capture all dispatch'd transactions
1022
- const result = callback(this.activeTransaction);
1023
-
1024
- // Any transactions captured by the `dispatch` call will be stored in `this.activeTransaction`
1025
- const activeTr = this.activeTransaction;
1026
-
1027
- this.activeTransaction = null;
1028
- if (
1029
- activeTr &&
1030
- // Only dispatch if the transaction was actually modified in some way
1031
- (activeTr.docChanged ||
1032
- activeTr.selectionSet ||
1033
- activeTr.scrolledIntoView ||
1034
- activeTr.storedMarksSet ||
1035
- !activeTr.isGeneric)
1036
- ) {
1037
- // Dispatch the transaction if it was modified
1038
- this._tiptapEditor.view.dispatch(activeTr);
1039
- }
1040
-
1041
- return result;
1042
- } finally {
1043
- // We wrap this in a finally block to ensure we don't disable future transactions just because of an error in the callback
1044
- this.activeTransaction = null;
1045
- }
1013
+ return this._stateManager.transact(callback);
1046
1014
  }
1047
1015
 
1048
1016
  // TO DISCUSS
@@ -1058,12 +1026,9 @@ export class BlockNoteEditor<
1058
1026
  ext: { new (...args: any[]): T } & typeof BlockNoteExtension,
1059
1027
  key = ext.key(),
1060
1028
  ): T {
1061
- const extension = this.extensions[key] as T;
1062
- if (!extension) {
1063
- throw new Error(`Extension ${key} not found`);
1064
- }
1065
- return extension;
1029
+ return this._extensionManager.extension(ext, key);
1066
1030
  }
1031
+
1067
1032
  /**
1068
1033
  * Mount the editor to a DOM element.
1069
1034
  *
@@ -1087,12 +1052,7 @@ export class BlockNoteEditor<
1087
1052
  * @see https://prosemirror.net/docs/ref/#state.EditorState
1088
1053
  */
1089
1054
  public get prosemirrorState() {
1090
- if (this.activeTransaction) {
1091
- throw new Error(
1092
- "`prosemirrorState` should not be called within a `transact` call, move the `prosemirrorState` call outside of the `transact` call or use `editor.transact` to read the current editor state",
1093
- );
1094
- }
1095
- return this._tiptapEditor.state;
1055
+ return this._stateManager.prosemirrorState;
1096
1056
  }
1097
1057
 
1098
1058
  /**
@@ -1100,7 +1060,7 @@ export class BlockNoteEditor<
1100
1060
  * @see https://prosemirror.net/docs/ref/#view.EditorView
1101
1061
  */
1102
1062
  public get prosemirrorView() {
1103
- return this._tiptapEditor.view;
1063
+ return this._stateManager.prosemirrorView;
1104
1064
  }
1105
1065
 
1106
1066
  public get domElement() {
@@ -1156,9 +1116,7 @@ export class BlockNoteEditor<
1156
1116
  * @returns A snapshot of all top-level (non-nested) blocks in the editor.
1157
1117
  */
1158
1118
  public get document(): Block<BSchema, ISchema, SSchema>[] {
1159
- return this.transact((tr) => {
1160
- return docToBlocks(tr.doc, this.pmSchema);
1161
- });
1119
+ return this._blockManager.document;
1162
1120
  }
1163
1121
 
1164
1122
  /**
@@ -1171,7 +1129,7 @@ export class BlockNoteEditor<
1171
1129
  public getBlock(
1172
1130
  blockIdentifier: BlockIdentifier,
1173
1131
  ): Block<BSchema, ISchema, SSchema> | undefined {
1174
- return this.transact((tr) => getBlock(tr.doc, blockIdentifier));
1132
+ return this._blockManager.getBlock(blockIdentifier);
1175
1133
  }
1176
1134
 
1177
1135
  /**
@@ -1186,7 +1144,7 @@ export class BlockNoteEditor<
1186
1144
  public getPrevBlock(
1187
1145
  blockIdentifier: BlockIdentifier,
1188
1146
  ): Block<BSchema, ISchema, SSchema> | undefined {
1189
- return this.transact((tr) => getPrevBlock(tr.doc, blockIdentifier));
1147
+ return this._blockManager.getPrevBlock(blockIdentifier);
1190
1148
  }
1191
1149
 
1192
1150
  /**
@@ -1200,7 +1158,7 @@ export class BlockNoteEditor<
1200
1158
  public getNextBlock(
1201
1159
  blockIdentifier: BlockIdentifier,
1202
1160
  ): Block<BSchema, ISchema, SSchema> | undefined {
1203
- return this.transact((tr) => getNextBlock(tr.doc, blockIdentifier));
1161
+ return this._blockManager.getNextBlock(blockIdentifier);
1204
1162
  }
1205
1163
 
1206
1164
  /**
@@ -1213,7 +1171,7 @@ export class BlockNoteEditor<
1213
1171
  public getParentBlock(
1214
1172
  blockIdentifier: BlockIdentifier,
1215
1173
  ): Block<BSchema, ISchema, SSchema> | undefined {
1216
- return this.transact((tr) => getParentBlock(tr.doc, blockIdentifier));
1174
+ return this._blockManager.getParentBlock(blockIdentifier);
1217
1175
  }
1218
1176
 
1219
1177
  /**
@@ -1225,33 +1183,7 @@ export class BlockNoteEditor<
1225
1183
  callback: (block: Block<BSchema, ISchema, SSchema>) => boolean,
1226
1184
  reverse = false,
1227
1185
  ): void {
1228
- const blocks = this.document.slice();
1229
-
1230
- if (reverse) {
1231
- blocks.reverse();
1232
- }
1233
-
1234
- function traverseBlockArray(
1235
- blockArray: Block<BSchema, ISchema, SSchema>[],
1236
- ): boolean {
1237
- for (const block of blockArray) {
1238
- if (callback(block) === false) {
1239
- return false;
1240
- }
1241
-
1242
- const children = reverse
1243
- ? block.children.slice().reverse()
1244
- : block.children;
1245
-
1246
- if (!traverseBlockArray(children)) {
1247
- return false;
1248
- }
1249
- }
1250
-
1251
- return true;
1252
- }
1253
-
1254
- traverseBlockArray(blocks);
1186
+ this._blockManager.forEachBlock(callback, reverse);
1255
1187
  }
1256
1188
 
1257
1189
  /**
@@ -1283,7 +1215,7 @@ export class BlockNoteEditor<
1283
1215
  ISchema,
1284
1216
  SSchema
1285
1217
  > {
1286
- return this.transact((tr) => getTextCursorPosition(tr));
1218
+ return this._selectionManager.getTextCursorPosition();
1287
1219
  }
1288
1220
 
1289
1221
  /**
@@ -1296,9 +1228,7 @@ export class BlockNoteEditor<
1296
1228
  targetBlock: BlockIdentifier,
1297
1229
  placement: "start" | "end" = "start",
1298
1230
  ) {
1299
- return this.transact((tr) =>
1300
- setTextCursorPosition(tr, targetBlock, placement),
1301
- );
1231
+ return this._selectionManager.setTextCursorPosition(targetBlock, placement);
1302
1232
  }
1303
1233
 
1304
1234
  /**
@@ -1308,7 +1238,7 @@ export class BlockNoteEditor<
1308
1238
  * If the selection starts / ends halfway through a block, the returned data will contain the entire block.
1309
1239
  */
1310
1240
  public getSelection(): Selection<BSchema, ISchema, SSchema> | undefined {
1311
- return this.transact((tr) => getSelection(tr));
1241
+ return this._selectionManager.getSelection();
1312
1242
  }
1313
1243
 
1314
1244
  /**
@@ -1319,7 +1249,7 @@ export class BlockNoteEditor<
1319
1249
  * only the part of the block that is included in the selection.
1320
1250
  */
1321
1251
  public getSelectionCutBlocks() {
1322
- return this.transact((tr) => getSelectionCutBlocks(tr));
1252
+ return this._selectionManager.getSelectionCutBlocks();
1323
1253
  }
1324
1254
 
1325
1255
  /**
@@ -1328,7 +1258,7 @@ export class BlockNoteEditor<
1328
1258
  * @param endBlock The identifier of the block that should be the end of the selection.
1329
1259
  */
1330
1260
  public setSelection(startBlock: BlockIdentifier, endBlock: BlockIdentifier) {
1331
- return this.transact((tr) => setSelection(tr, startBlock, endBlock));
1261
+ return this._selectionManager.setSelection(startBlock, endBlock);
1332
1262
  }
1333
1263
 
1334
1264
  /**
@@ -1336,9 +1266,7 @@ export class BlockNoteEditor<
1336
1266
  * @returns True if the editor is editable, false otherwise.
1337
1267
  */
1338
1268
  public get isEditable(): boolean {
1339
- return this._tiptapEditor.isEditable === undefined
1340
- ? true
1341
- : this._tiptapEditor.isEditable;
1269
+ return this._stateManager.isEditable;
1342
1270
  }
1343
1271
 
1344
1272
  /**
@@ -1346,9 +1274,7 @@ export class BlockNoteEditor<
1346
1274
  * @param editable True to make the editor editable, or false to lock it.
1347
1275
  */
1348
1276
  public set isEditable(editable: boolean) {
1349
- if (this._tiptapEditor.options.editable !== editable) {
1350
- this._tiptapEditor.setEditable(editable);
1351
- }
1277
+ this._stateManager.isEditable = editable;
1352
1278
  }
1353
1279
 
1354
1280
  /**
@@ -1364,8 +1290,10 @@ export class BlockNoteEditor<
1364
1290
  referenceBlock: BlockIdentifier,
1365
1291
  placement: "before" | "after" = "before",
1366
1292
  ) {
1367
- return this.transact((tr) =>
1368
- insertBlocks(tr, blocksToInsert, referenceBlock, placement),
1293
+ return this._blockManager.insertBlocks(
1294
+ blocksToInsert,
1295
+ referenceBlock,
1296
+ placement,
1369
1297
  );
1370
1298
  }
1371
1299
 
@@ -1380,7 +1308,7 @@ export class BlockNoteEditor<
1380
1308
  blockToUpdate: BlockIdentifier,
1381
1309
  update: PartialBlock<BSchema, ISchema, SSchema>,
1382
1310
  ) {
1383
- return this.transact((tr) => updateBlock(tr, blockToUpdate, update));
1311
+ return this._blockManager.updateBlock(blockToUpdate, update);
1384
1312
  }
1385
1313
 
1386
1314
  /**
@@ -1388,9 +1316,7 @@ export class BlockNoteEditor<
1388
1316
  * @param blocksToRemove An array of identifiers for existing blocks that should be removed.
1389
1317
  */
1390
1318
  public removeBlocks(blocksToRemove: BlockIdentifier[]) {
1391
- return this.transact(
1392
- (tr) => removeAndInsertBlocks(tr, blocksToRemove, []).removedBlocks,
1393
- );
1319
+ return this._blockManager.removeBlocks(blocksToRemove);
1394
1320
  }
1395
1321
 
1396
1322
  /**
@@ -1404,30 +1330,21 @@ export class BlockNoteEditor<
1404
1330
  blocksToRemove: BlockIdentifier[],
1405
1331
  blocksToInsert: PartialBlock<BSchema, ISchema, SSchema>[],
1406
1332
  ) {
1407
- return this.transact((tr) =>
1408
- removeAndInsertBlocks(tr, blocksToRemove, blocksToInsert),
1409
- );
1333
+ return this._blockManager.replaceBlocks(blocksToRemove, blocksToInsert);
1410
1334
  }
1411
1335
 
1412
1336
  /**
1413
1337
  * Undo the last action.
1414
1338
  */
1415
1339
  public undo() {
1416
- if (this.options.collaboration) {
1417
- return this.exec(undoCommand);
1418
- }
1419
-
1420
- return this.exec(undo);
1340
+ return this._stateManager.undo();
1421
1341
  }
1422
1342
 
1423
1343
  /**
1424
1344
  * Redo the last action.
1425
1345
  */
1426
1346
  public redo() {
1427
- if (this.options.collaboration) {
1428
- return this.exec(redoCommand);
1429
- }
1430
- return this.exec(redo);
1347
+ return this._stateManager.redo();
1431
1348
  }
1432
1349
 
1433
1350
  /**
@@ -1439,55 +1356,14 @@ export class BlockNoteEditor<
1439
1356
  content: PartialInlineContent<ISchema, SSchema>,
1440
1357
  { updateSelection = false }: { updateSelection?: boolean } = {},
1441
1358
  ) {
1442
- const nodes = inlineContentToNodes(content, this.pmSchema);
1443
-
1444
- this.transact((tr) => {
1445
- insertContentAt(
1446
- tr,
1447
- {
1448
- from: tr.selection.from,
1449
- to: tr.selection.to,
1450
- },
1451
- nodes,
1452
- {
1453
- updateSelection,
1454
- },
1455
- );
1456
- });
1359
+ this._styleManager.insertInlineContent(content, { updateSelection });
1457
1360
  }
1458
1361
 
1459
1362
  /**
1460
1363
  * Gets the active text styles at the text cursor position or at the end of the current selection if it's active.
1461
1364
  */
1462
- public getActiveStyles() {
1463
- return this.transact((tr) => {
1464
- const styles: Styles<SSchema> = {};
1465
- const marks = tr.selection.$to.marks();
1466
-
1467
- for (const mark of marks) {
1468
- const config = this.schema.styleSchema[mark.type.name];
1469
- if (!config) {
1470
- if (
1471
- // Links are not considered styles in blocknote
1472
- mark.type.name !== "link" &&
1473
- // "blocknoteIgnore" tagged marks (such as comments) are also not considered BlockNote "styles"
1474
- !mark.type.spec.blocknoteIgnore
1475
- ) {
1476
- // eslint-disable-next-line no-console
1477
- console.warn("mark not found in styleschema", mark.type.name);
1478
- }
1479
-
1480
- continue;
1481
- }
1482
- if (config.propSchema === "boolean") {
1483
- (styles as any)[config.type] = true;
1484
- } else {
1485
- (styles as any)[config.type] = mark.attrs.stringValue;
1486
- }
1487
- }
1488
-
1489
- return styles;
1490
- });
1365
+ public getActiveStyles(): Styles<SSchema> {
1366
+ return this._styleManager.getActiveStyles();
1491
1367
  }
1492
1368
 
1493
1369
  /**
@@ -1495,19 +1371,7 @@ export class BlockNoteEditor<
1495
1371
  * @param styles The styles to add.
1496
1372
  */
1497
1373
  public addStyles(styles: Styles<SSchema>) {
1498
- for (const [style, value] of Object.entries(styles)) {
1499
- const config = this.schema.styleSchema[style];
1500
- if (!config) {
1501
- throw new Error(`style ${style} not found in styleSchema`);
1502
- }
1503
- if (config.propSchema === "boolean") {
1504
- this._tiptapEditor.commands.setMark(style);
1505
- } else if (config.propSchema === "string") {
1506
- this._tiptapEditor.commands.setMark(style, { stringValue: value });
1507
- } else {
1508
- throw new UnreachableCaseError(config.propSchema);
1509
- }
1510
- }
1374
+ this._styleManager.addStyles(styles);
1511
1375
  }
1512
1376
 
1513
1377
  /**
@@ -1515,9 +1379,7 @@ export class BlockNoteEditor<
1515
1379
  * @param styles The styles to remove.
1516
1380
  */
1517
1381
  public removeStyles(styles: Styles<SSchema>) {
1518
- for (const style of Object.keys(styles)) {
1519
- this._tiptapEditor.commands.unsetMark(style);
1520
- }
1382
+ this._styleManager.removeStyles(styles);
1521
1383
  }
1522
1384
 
1523
1385
  /**
@@ -1525,35 +1387,21 @@ export class BlockNoteEditor<
1525
1387
  * @param styles The styles to toggle.
1526
1388
  */
1527
1389
  public toggleStyles(styles: Styles<SSchema>) {
1528
- for (const [style, value] of Object.entries(styles)) {
1529
- const config = this.schema.styleSchema[style];
1530
- if (!config) {
1531
- throw new Error(`style ${style} not found in styleSchema`);
1532
- }
1533
- if (config.propSchema === "boolean") {
1534
- this._tiptapEditor.commands.toggleMark(style);
1535
- } else if (config.propSchema === "string") {
1536
- this._tiptapEditor.commands.toggleMark(style, { stringValue: value });
1537
- } else {
1538
- throw new UnreachableCaseError(config.propSchema);
1539
- }
1540
- }
1390
+ this._styleManager.toggleStyles(styles);
1541
1391
  }
1542
1392
 
1543
1393
  /**
1544
1394
  * Gets the currently selected text.
1545
1395
  */
1546
1396
  public getSelectedText() {
1547
- return this.transact((tr) => {
1548
- return tr.doc.textBetween(tr.selection.from, tr.selection.to);
1549
- });
1397
+ return this._styleManager.getSelectedText();
1550
1398
  }
1551
1399
 
1552
1400
  /**
1553
1401
  * Gets the URL of the last link in the current selection, or `undefined` if there are no links in the selection.
1554
1402
  */
1555
1403
  public getSelectedLinkUrl() {
1556
- return this._tiptapEditor.getAttributes("link").href as string | undefined;
1404
+ return this._styleManager.getSelectedLinkUrl();
1557
1405
  }
1558
1406
 
1559
1407
  /**
@@ -1562,51 +1410,35 @@ export class BlockNoteEditor<
1562
1410
  * @param text The text to display the link with.
1563
1411
  */
1564
1412
  public createLink(url: string, text?: string) {
1565
- if (url === "") {
1566
- return;
1567
- }
1568
- const mark = this.pmSchema.mark("link", { href: url });
1569
- this.transact((tr) => {
1570
- const { from, to } = tr.selection;
1571
-
1572
- if (text) {
1573
- tr.insertText(text, from, to).addMark(from, from + text.length, mark);
1574
- } else {
1575
- tr.setSelection(TextSelection.create(tr.doc, to)).addMark(
1576
- from,
1577
- to,
1578
- mark,
1579
- );
1580
- }
1581
- });
1413
+ this._styleManager.createLink(url, text);
1582
1414
  }
1583
1415
 
1584
1416
  /**
1585
1417
  * Checks if the block containing the text cursor can be nested.
1586
1418
  */
1587
1419
  public canNestBlock() {
1588
- return canNestBlock(this);
1420
+ return this._blockManager.canNestBlock();
1589
1421
  }
1590
1422
 
1591
1423
  /**
1592
1424
  * Nests the block containing the text cursor into the block above it.
1593
1425
  */
1594
1426
  public nestBlock() {
1595
- nestBlock(this);
1427
+ this._blockManager.nestBlock();
1596
1428
  }
1597
1429
 
1598
1430
  /**
1599
1431
  * Checks if the block containing the text cursor is nested.
1600
1432
  */
1601
1433
  public canUnnestBlock() {
1602
- return canUnnestBlock(this);
1434
+ return this._blockManager.canUnnestBlock();
1603
1435
  }
1604
1436
 
1605
1437
  /**
1606
1438
  * Lifts the block containing the text cursor out of its parent.
1607
1439
  */
1608
1440
  public unnestBlock() {
1609
- unnestBlock(this);
1441
+ this._blockManager.unnestBlock();
1610
1442
  }
1611
1443
 
1612
1444
  /**
@@ -1615,7 +1447,7 @@ export class BlockNoteEditor<
1615
1447
  * current blocks share a common parent, moves them out of & before it.
1616
1448
  */
1617
1449
  public moveBlocksUp() {
1618
- return moveBlocksUp(this);
1450
+ return this._blockManager.moveBlocksUp();
1619
1451
  }
1620
1452
 
1621
1453
  /**
@@ -1624,7 +1456,7 @@ export class BlockNoteEditor<
1624
1456
  * current blocks share a common parent, moves them out of & after it.
1625
1457
  */
1626
1458
  public moveBlocksDown() {
1627
- return moveBlocksDown(this);
1459
+ return this._blockManager.moveBlocksDown();
1628
1460
  }
1629
1461
 
1630
1462
  /**
@@ -1637,8 +1469,7 @@ export class BlockNoteEditor<
1637
1469
  public blocksToHTMLLossy(
1638
1470
  blocks: PartialBlock<BSchema, ISchema, SSchema>[] = this.document,
1639
1471
  ): string {
1640
- const exporter = createExternalHTMLExporter(this.pmSchema, this);
1641
- return exporter.exportBlocks(blocks, {});
1472
+ return this._exportManager.blocksToHTMLLossy(blocks);
1642
1473
  }
1643
1474
 
1644
1475
  /**
@@ -1653,8 +1484,7 @@ export class BlockNoteEditor<
1653
1484
  public blocksToFullHTML(
1654
1485
  blocks: PartialBlock<BSchema, ISchema, SSchema>[],
1655
1486
  ): string {
1656
- const exporter = createInternalHTMLSerializer(this.pmSchema, this);
1657
- return exporter.serializeBlocks(blocks, {});
1487
+ return this._exportManager.blocksToFullHTML(blocks);
1658
1488
  }
1659
1489
  /**
1660
1490
  * Parses blocks from an HTML string. Tries to create `Block` objects out of any HTML block-level elements, and
@@ -1666,7 +1496,7 @@ export class BlockNoteEditor<
1666
1496
  public tryParseHTMLToBlocks(
1667
1497
  html: string,
1668
1498
  ): Block<BSchema, ISchema, SSchema>[] {
1669
- return HTMLToBlocks(html, this.pmSchema);
1499
+ return this._exportManager.tryParseHTMLToBlocks(html);
1670
1500
  }
1671
1501
 
1672
1502
  /**
@@ -1678,7 +1508,7 @@ export class BlockNoteEditor<
1678
1508
  public blocksToMarkdownLossy(
1679
1509
  blocks: PartialBlock<BSchema, ISchema, SSchema>[] = this.document,
1680
1510
  ): string {
1681
- return blocksToMarkdown(blocks, this.pmSchema, this, {});
1511
+ return this._exportManager.blocksToMarkdownLossy(blocks);
1682
1512
  }
1683
1513
 
1684
1514
  /**
@@ -1688,43 +1518,23 @@ export class BlockNoteEditor<
1688
1518
  * @param markdown The Markdown string to parse blocks from.
1689
1519
  * @returns The blocks parsed from the Markdown string.
1690
1520
  */
1691
- public async tryParseMarkdownToBlocks(
1521
+ public tryParseMarkdownToBlocks(
1692
1522
  markdown: string,
1693
- ): Promise<Block<BSchema, ISchema, SSchema>[]> {
1694
- return markdownToBlocks(markdown, this.pmSchema);
1523
+ ): Block<BSchema, ISchema, SSchema>[] {
1524
+ return this._exportManager.tryParseMarkdownToBlocks(markdown);
1695
1525
  }
1696
1526
 
1697
1527
  /**
1698
1528
  * Updates the user info for the current user that's shown to other collaborators.
1699
1529
  */
1700
1530
  public updateCollaborationUserInfo(user: { name: string; color: string }) {
1701
- if (!this.options.collaboration) {
1531
+ if (!this._collaborationManager) {
1702
1532
  throw new Error(
1703
1533
  "Cannot update collaboration user info when collaboration is disabled.",
1704
1534
  );
1705
1535
  }
1706
1536
 
1707
- (this.extensions["yCursorPlugin"] as CursorPlugin).updateUser(user);
1708
- }
1709
-
1710
- /**
1711
- * Registers a callback which will be called before any change is applied to the editor, allowing you to cancel the change.
1712
- */
1713
- public onBeforeChange(
1714
- /**
1715
- * If the callback returns `false`, the change will be canceled & not applied to the editor.
1716
- */
1717
- callback: (
1718
- editor: BlockNoteEditor<BSchema, ISchema, SSchema>,
1719
- context: {
1720
- getChanges: () => BlocksChanged<BSchema, ISchema, SSchema>;
1721
- tr: Transaction;
1722
- },
1723
- ) => boolean | void,
1724
- ): () => void {
1725
- return (this.extensions["blockChange"] as BlockChangePlugin).subscribe(
1726
- (context) => callback(this, context),
1727
- );
1537
+ this._collaborationManager.updateUserInfo(user);
1728
1538
  }
1729
1539
 
1730
1540
  /**
@@ -1744,24 +1554,7 @@ export class BlockNoteEditor<
1744
1554
  },
1745
1555
  ) => void,
1746
1556
  ) {
1747
- const cb = ({
1748
- transaction,
1749
- appendedTransactions,
1750
- }: {
1751
- transaction: Transaction;
1752
- appendedTransactions: Transaction[];
1753
- }) => {
1754
- callback(this, {
1755
- getChanges: () =>
1756
- getBlocksChangedByTransaction(transaction, appendedTransactions),
1757
- });
1758
- };
1759
-
1760
- this._tiptapEditor.on("update", cb);
1761
-
1762
- return () => {
1763
- this._tiptapEditor.off("update", cb);
1764
- };
1557
+ return this._eventManager.onChange(callback);
1765
1558
  }
1766
1559
 
1767
1560
  /**
@@ -1774,29 +1567,19 @@ export class BlockNoteEditor<
1774
1567
  callback: (editor: BlockNoteEditor<BSchema, ISchema, SSchema>) => void,
1775
1568
  includeSelectionChangedByRemote?: boolean,
1776
1569
  ) {
1777
- const cb = (e: { transaction: Transaction }) => {
1778
- if (
1779
- e.transaction.getMeta(ySyncPluginKey) &&
1780
- !includeSelectionChangedByRemote
1781
- ) {
1782
- // selection changed because of a yjs sync (i.e.: other user was typing)
1783
- // we don't want to trigger the callback in this case
1784
- return;
1785
- }
1786
- callback(this);
1787
- };
1788
-
1789
- this._tiptapEditor.on("selectionUpdate", cb);
1790
-
1791
- return () => {
1792
- this._tiptapEditor.off("selectionUpdate", cb);
1793
- };
1570
+ return this._eventManager.onSelectionChange(
1571
+ callback,
1572
+ includeSelectionChangedByRemote,
1573
+ );
1794
1574
  }
1795
1575
 
1796
1576
  /**
1797
1577
  * A callback function that runs when the editor has been initialized.
1798
1578
  *
1799
1579
  * This can be useful for plugins to initialize themselves after the editor has been initialized.
1580
+ *
1581
+ * @param callback The callback to execute.
1582
+ * @returns A function to remove the callback.
1800
1583
  */
1801
1584
  public onCreate(callback: () => void) {
1802
1585
  this.on("create", callback);
@@ -1806,26 +1589,44 @@ export class BlockNoteEditor<
1806
1589
  };
1807
1590
  }
1808
1591
 
1809
- public getSelectionBoundingBox() {
1810
- if (!this.prosemirrorView) {
1811
- return undefined;
1812
- }
1813
-
1814
- const { selection } = this.prosemirrorState;
1815
-
1816
- // support for CellSelections
1817
- const { ranges } = selection;
1818
- const from = Math.min(...ranges.map((range) => range.$from.pos));
1819
- const to = Math.max(...ranges.map((range) => range.$to.pos));
1592
+ /**
1593
+ * A callback function that runs when the editor has been mounted.
1594
+ *
1595
+ * This can be useful for plugins to initialize themselves after the editor has been mounted.
1596
+ *
1597
+ * @param callback The callback to execute.
1598
+ * @returns A function to remove the callback.
1599
+ */
1600
+ public onMount(
1601
+ callback: (ctx: {
1602
+ editor: BlockNoteEditor<BSchema, ISchema, SSchema>;
1603
+ }) => void,
1604
+ ) {
1605
+ this._eventManager.onMount(callback);
1606
+ }
1820
1607
 
1821
- if (isNodeSelection(selection)) {
1822
- const node = this.prosemirrorView.nodeDOM(from) as HTMLElement;
1823
- if (node) {
1824
- return node.getBoundingClientRect();
1825
- }
1826
- }
1608
+ /**
1609
+ * A callback function that runs when the editor has been unmounted.
1610
+ *
1611
+ * This can be useful for plugins to clean up themselves after the editor has been unmounted.
1612
+ *
1613
+ * @param callback The callback to execute.
1614
+ * @returns A function to remove the callback.
1615
+ */
1616
+ public onUnmount(
1617
+ callback: (ctx: {
1618
+ editor: BlockNoteEditor<BSchema, ISchema, SSchema>;
1619
+ }) => void,
1620
+ ) {
1621
+ this._eventManager.onUnmount(callback);
1622
+ }
1827
1623
 
1828
- return posToDOMRect(this.prosemirrorView, from, to);
1624
+ /**
1625
+ * Gets the bounding box of the current selection.
1626
+ * @returns The bounding box of the current selection.
1627
+ */
1628
+ public getSelectionBoundingBox() {
1629
+ return this._selectionManager.getSelectionBoundingBox();
1829
1630
  }
1830
1631
 
1831
1632
  public get isEmpty() {
@@ -1883,15 +1684,7 @@ export class BlockNoteEditor<
1883
1684
  * @param raw Whether to paste the HTML as is, or to convert it to BlockNote HTML.
1884
1685
  */
1885
1686
  public pasteHTML(html: string, raw = false) {
1886
- let htmlToPaste = html;
1887
- if (!raw) {
1888
- const blocks = this.tryParseHTMLToBlocks(html);
1889
- htmlToPaste = this.blocksToFullHTML(blocks);
1890
- }
1891
- if (!htmlToPaste) {
1892
- return;
1893
- }
1894
- this.prosemirrorView?.pasteHTML(htmlToPaste);
1687
+ this._exportManager.pasteHTML(html, raw);
1895
1688
  }
1896
1689
 
1897
1690
  /**
@@ -1899,7 +1692,7 @@ export class BlockNoteEditor<
1899
1692
  * @param text The text to paste.
1900
1693
  */
1901
1694
  public pasteText(text: string) {
1902
- return this.prosemirrorView?.pasteText(text);
1695
+ return this._exportManager.pasteText(text);
1903
1696
  }
1904
1697
 
1905
1698
  /**
@@ -1907,7 +1700,6 @@ export class BlockNoteEditor<
1907
1700
  * @param markdown The markdown to paste.
1908
1701
  */
1909
1702
  public pasteMarkdown(markdown: string) {
1910
- const html = markdownToHTML(markdown);
1911
- return this.pasteHTML(html);
1703
+ return this._exportManager.pasteMarkdown(markdown);
1912
1704
  }
1913
1705
  }