1
- import { EditorOptions , Extension , getSchema } from "@tiptap/core" ;
1
+ import {
2
+ AnyExtension ,
3
+ EditorOptions ,
4
+ Extension ,
5
+ getSchema ,
6
+ Mark ,
7
+ Node as TipTapNode ,
8
+ } from "@tiptap/core" ;
2
9
import { Node , Schema } from "prosemirror-model" ;
3
10
// import "./blocknote.css";
4
11
import * as Y from "yjs" ;
@@ -47,9 +54,9 @@ import {
47
54
InlineContentSchema ,
48
55
InlineContentSpecs ,
49
56
PartialInlineContent ,
57
+ Styles ,
50
58
StyleSchema ,
51
59
StyleSpecs ,
52
- Styles ,
53
60
} from "../schema/index.js" ;
54
61
import { mergeCSSClasses } from "../util/browser.js" ;
55
62
import { NoInfer , UnreachableCaseError } from "../util/typescript.js" ;
@@ -67,7 +74,6 @@ import {
67
74
BlockNoteTipTapEditorOptions ,
68
75
} from "./BlockNoteTipTapEditor.js" ;
69
76
70
- import { PlaceholderPlugin } from "../extensions/Placeholder/PlaceholderPlugin.js" ;
71
77
import { Dictionary } from "../i18n/dictionary.js" ;
72
78
import { en } from "../i18n/locales/index.js" ;
73
79
@@ -76,10 +82,14 @@ import { dropCursor } from "prosemirror-dropcursor";
76
82
import { createInternalHTMLSerializer } from "../api/exporters/html/internalHTMLSerializer.js" ;
77
83
import { inlineContentToNodes } from "../api/nodeConversions/blockToNode.js" ;
78
84
import { nodeToBlock } from "../api/nodeConversions/nodeToBlock.js" ;
79
- import { NodeSelectionKeyboardPlugin } from "../extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.js" ;
80
- import { PreviousBlockTypePlugin } from "../extensions/PreviousBlockType/PreviousBlockTypePlugin.js" ;
81
85
import "../style.css" ;
82
86
87
+ export type BlockNoteExtension =
88
+ | AnyExtension
89
+ | {
90
+ plugin : Plugin ;
91
+ } ;
92
+
83
93
export type BlockNoteEditorOptions <
84
94
BSchema extends BlockSchema ,
85
95
ISchema extends InlineContentSchema ,
@@ -92,7 +102,11 @@ export type BlockNoteEditorOptions<
92
102
*/
93
103
animations ?: boolean ;
94
104
105
+ /**
106
+ * Disable internal extensions (based on keys / extension name)
107
+ */
95
108
disableExtensions : string [ ] ;
109
+
96
110
/**
97
111
* A dictionary object containing translations for the editor.
98
112
*/
@@ -173,9 +187,16 @@ export type BlockNoteEditorOptions<
173
187
renderCursor ?: ( user : any ) => HTMLElement ;
174
188
} ;
175
189
176
- // tiptap options, undocumented
190
+ /**
191
+ * additional tiptap options, undocumented
192
+ */
177
193
_tiptapOptions : Partial < EditorOptions > ;
178
194
195
+ /**
196
+ * (experimental) add extra prosemirror plugins or tiptap extensions to the editor
197
+ */
198
+ _extensions : Record < string , BlockNoteExtension > ;
199
+
179
200
trailingBlock ?: boolean ;
180
201
181
202
/**
@@ -213,6 +234,11 @@ export class BlockNoteEditor<
213
234
> {
214
235
private readonly _pmSchema : Schema ;
215
236
237
+ /**
238
+ * extensions that are added to the editor, can be tiptap extensions or prosemirror plugins
239
+ */
240
+ public readonly extensions : Record < string , BlockNoteExtension > = { } ;
241
+
216
242
/**
217
243
* Boolean indicating whether the editor is in headless mode.
218
244
* Headless mode means we can use features like importing / exporting blocks,
@@ -355,17 +381,7 @@ export class BlockNoteEditor<
355
381
this . inlineContentImplementations = newOptions . schema . inlineContentSpecs ;
356
382
this . styleImplementations = newOptions . schema . styleSpecs ;
357
383
358
- this . formattingToolbar = new FormattingToolbarProsemirrorPlugin ( this ) ;
359
- this . linkToolbar = new LinkToolbarProsemirrorPlugin ( this ) ;
360
- this . sideMenu = new SideMenuProsemirrorPlugin ( this ) ;
361
- this . suggestionMenus = new SuggestionMenuProseMirrorPlugin ( this ) ;
362
- this . filePanel = new FilePanelProsemirrorPlugin ( this as any ) ;
363
-
364
- if ( checkDefaultBlockTypeInSchema ( "table" , this ) ) {
365
- this . tableHandles = new TableHandlesProsemirrorPlugin ( this as any ) ;
366
- }
367
-
368
- const extensions = getBlockNoteExtensions ( {
384
+ this . extensions = getBlockNoteExtensions ( {
369
385
editor : this ,
370
386
domAttributes : newOptions . domAttributes || { } ,
371
387
blockSpecs : this . schema . blockSpecs ,
@@ -375,30 +391,28 @@ export class BlockNoteEditor<
375
391
trailingBlock : newOptions . trailingBlock ,
376
392
disableExtensions : newOptions . disableExtensions ,
377
393
setIdAttribute : newOptions . setIdAttribute ,
394
+ animations : newOptions . animations ?? true ,
395
+ tableHandles : checkDefaultBlockTypeInSchema ( "table" , this ) ,
396
+ dropCursor : this . options . dropCursor ?? dropCursor ,
397
+ placeholders : newOptions . placeholders ,
378
398
} ) ;
379
399
380
- const dropCursorPlugin : any = this . options . dropCursor ?? dropCursor ;
381
- const blockNoteUIExtension = Extension . create ( {
382
- name : "BlockNoteUIExtension" ,
383
-
384
- addProseMirrorPlugins : ( ) => {
385
- return [
386
- this . formattingToolbar . plugin ,
387
- this . linkToolbar . plugin ,
388
- this . sideMenu . plugin ,
389
- this . suggestionMenus . plugin ,
390
- ...( this . filePanel ? [ this . filePanel . plugin ] : [ ] ) ,
391
- ...( this . tableHandles ? [ this . tableHandles . plugin ] : [ ] ) ,
392
- dropCursorPlugin ( { width : 5 , color : "#ddeeff" , editor : this } ) ,
393
- PlaceholderPlugin ( this , newOptions . placeholders ) ,
394
- NodeSelectionKeyboardPlugin ( ) ,
395
- ...( this . options . animations ?? true
396
- ? [ PreviousBlockTypePlugin ( ) ]
397
- : [ ] ) ,
398
- ] ;
399
- } ,
400
+ // add extensions from _tiptapOptions
401
+ ( newOptions . _tiptapOptions ?. extensions || [ ] ) . forEach ( ( ext ) => {
402
+ this . extensions [ ext . name ] = ext ;
403
+ } ) ;
404
+
405
+ // add extensions from options
406
+ Object . entries ( newOptions . _extensions || { } ) . forEach ( ( [ key , ext ] ) => {
407
+ this . extensions [ key ] = ext ;
400
408
} ) ;
401
- extensions . push ( blockNoteUIExtension ) ;
409
+
410
+ this . formattingToolbar = this . extensions [ "formattingToolbar" ] as any ;
411
+ this . linkToolbar = this . extensions [ "linkToolbar" ] as any ;
412
+ this . sideMenu = this . extensions [ "sideMenu" ] as any ;
413
+ this . suggestionMenus = this . extensions [ "suggestionMenus" ] as any ;
414
+ this . filePanel = this . extensions [ "filePanel" ] as any ;
415
+ this . tableHandles = this . extensions [ "tableHandles" ] as any ;
402
416
403
417
if ( newOptions . uploadFile ) {
404
418
const uploadFile = newOptions . uploadFile ;
@@ -449,14 +463,29 @@ export class BlockNoteEditor<
449
463
) ;
450
464
}
451
465
466
+ const tiptapExtensions = [
467
+ ...Object . entries ( this . extensions ) . map ( ( [ key , ext ] ) => {
468
+ if (
469
+ ext instanceof Extension ||
470
+ ext instanceof TipTapNode ||
471
+ ext instanceof Mark
472
+ ) {
473
+ // tiptap extension
474
+ return ext ;
475
+ }
476
+
477
+ // "blocknote" extensions (prosemirror plugins)
478
+ return Extension . create ( {
479
+ name : key ,
480
+ addProseMirrorPlugins : ( ) => [ ext . plugin ] ,
481
+ } ) ;
482
+ } ) ,
483
+ ] ;
452
484
const tiptapOptions : BlockNoteTipTapEditorOptions = {
453
485
...blockNoteTipTapOptions ,
454
486
...newOptions . _tiptapOptions ,
455
487
content : initialContent ,
456
- extensions : [
457
- ...( newOptions . _tiptapOptions ?. extensions || [ ] ) ,
458
- ...extensions ,
459
- ] ,
488
+ extensions : tiptapExtensions ,
460
489
editorProps : {
461
490
...newOptions . _tiptapOptions ?. editorProps ,
462
491
attributes : {
0 commit comments