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" ;
29import { Node , Schema } from "prosemirror-model" ;
310// import "./blocknote.css";
411import * as Y from "yjs" ;
@@ -47,9 +54,9 @@ import {
4754 InlineContentSchema ,
4855 InlineContentSpecs ,
4956 PartialInlineContent ,
57+ Styles ,
5058 StyleSchema ,
5159 StyleSpecs ,
52- Styles ,
5360} from "../schema/index.js" ;
5461import { mergeCSSClasses } from "../util/browser.js" ;
5562import { NoInfer , UnreachableCaseError } from "../util/typescript.js" ;
@@ -67,7 +74,6 @@ import {
6774 BlockNoteTipTapEditorOptions ,
6875} from "./BlockNoteTipTapEditor.js" ;
6976
70- import { PlaceholderPlugin } from "../extensions/Placeholder/PlaceholderPlugin.js" ;
7177import { Dictionary } from "../i18n/dictionary.js" ;
7278import { en } from "../i18n/locales/index.js" ;
7379
@@ -76,10 +82,14 @@ import { dropCursor } from "prosemirror-dropcursor";
7682import { createInternalHTMLSerializer } from "../api/exporters/html/internalHTMLSerializer.js" ;
7783import { inlineContentToNodes } from "../api/nodeConversions/blockToNode.js" ;
7884import { nodeToBlock } from "../api/nodeConversions/nodeToBlock.js" ;
79- import { NodeSelectionKeyboardPlugin } from "../extensions/NodeSelectionKeyboard/NodeSelectionKeyboardPlugin.js" ;
80- import { PreviousBlockTypePlugin } from "../extensions/PreviousBlockType/PreviousBlockTypePlugin.js" ;
8185import "../style.css" ;
8286
87+ export type BlockNoteExtension =
88+ | AnyExtension
89+ | {
90+ plugin : Plugin ;
91+ } ;
92+
8393export type BlockNoteEditorOptions <
8494 BSchema extends BlockSchema ,
8595 ISchema extends InlineContentSchema ,
@@ -92,7 +102,11 @@ export type BlockNoteEditorOptions<
92102 */
93103 animations ?: boolean ;
94104
105+ /**
106+ * Disable internal extensions (based on keys / extension name)
107+ */
95108 disableExtensions : string [ ] ;
109+
96110 /**
97111 * A dictionary object containing translations for the editor.
98112 */
@@ -173,9 +187,16 @@ export type BlockNoteEditorOptions<
173187 renderCursor ?: ( user : any ) => HTMLElement ;
174188 } ;
175189
176- // tiptap options, undocumented
190+ /**
191+ * additional tiptap options, undocumented
192+ */
177193 _tiptapOptions : Partial < EditorOptions > ;
178194
195+ /**
196+ * (experimental) add extra prosemirror plugins or tiptap extensions to the editor
197+ */
198+ _extensions : Record < string , BlockNoteExtension > ;
199+
179200 trailingBlock ?: boolean ;
180201
181202 /**
@@ -213,6 +234,11 @@ export class BlockNoteEditor<
213234> {
214235 private readonly _pmSchema : Schema ;
215236
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+
216242 /**
217243 * Boolean indicating whether the editor is in headless mode.
218244 * Headless mode means we can use features like importing / exporting blocks,
@@ -355,17 +381,7 @@ export class BlockNoteEditor<
355381 this . inlineContentImplementations = newOptions . schema . inlineContentSpecs ;
356382 this . styleImplementations = newOptions . schema . styleSpecs ;
357383
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 ( {
369385 editor : this ,
370386 domAttributes : newOptions . domAttributes || { } ,
371387 blockSpecs : this . schema . blockSpecs ,
@@ -375,30 +391,28 @@ export class BlockNoteEditor<
375391 trailingBlock : newOptions . trailingBlock ,
376392 disableExtensions : newOptions . disableExtensions ,
377393 setIdAttribute : newOptions . setIdAttribute ,
394+ animations : newOptions . animations ?? true ,
395+ tableHandles : checkDefaultBlockTypeInSchema ( "table" , this ) ,
396+ dropCursor : this . options . dropCursor ?? dropCursor ,
397+ placeholders : newOptions . placeholders ,
378398 } ) ;
379399
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 ;
400408 } ) ;
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 ;
402416
403417 if ( newOptions . uploadFile ) {
404418 const uploadFile = newOptions . uploadFile ;
@@ -449,14 +463,29 @@ export class BlockNoteEditor<
449463 ) ;
450464 }
451465
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+ ] ;
452484 const tiptapOptions : BlockNoteTipTapEditorOptions = {
453485 ...blockNoteTipTapOptions ,
454486 ...newOptions . _tiptapOptions ,
455487 content : initialContent ,
456- extensions : [
457- ...( newOptions . _tiptapOptions ?. extensions || [ ] ) ,
458- ...extensions ,
459- ] ,
488+ extensions : tiptapExtensions ,
460489 editorProps : {
461490 ...newOptions . _tiptapOptions ?. editorProps ,
462491 attributes : {
0 commit comments