1- import { InputRule , inputRules } from "@handlewithcare/prosemirror-inputrules" ;
1+ import {
2+ InputRule ,
3+ inputRules as inputRulesPlugin ,
4+ } from "@handlewithcare/prosemirror-inputrules" ;
25import {
36 AnyExtension as AnyTiptapExtension ,
47 Extension as TiptapExtension ,
@@ -147,9 +150,20 @@ export class ExtensionManager {
147150 ) ;
148151 }
149152
150- this . getProsemirrorPluginsFromExtension ( extension ) . forEach ( ( plugin ) => {
151- pluginsToAdd . add ( plugin ) ;
152- } ) ;
153+ if ( extension ?. inputRules ?. length ) {
154+ // This is necessary because input rules are defined in a single prosemirror plugin which cannot be re-initialized.
155+ // eslint-disable-next-line no-console
156+ console . warn (
157+ `Extension ${ extension . key } has input rules, but these cannot be changed after initializing the editor. Please separate the extension into multiple extensions if you want to add them, or re-initialize the editor.` ,
158+ extension ,
159+ ) ;
160+ }
161+
162+ this . getProsemirrorPluginsFromExtension ( extension ) . plugins . forEach (
163+ ( plugin ) => {
164+ pluginsToAdd . add ( plugin ) ;
165+ } ,
166+ ) ;
153167 }
154168
155169 // TODO there isn't a great way to do sorting right now. This is something that should be improved in the future.
@@ -308,25 +322,55 @@ export class ExtensionManager {
308322
309323 const getPriority = sortByDependencies ( this . extensions ) ;
310324
325+ const inputRulesByPriority = new Map < number , InputRule [ ] > ( ) ;
311326 for ( const extension of this . extensions ) {
312327 if ( extension . tiptapExtensions ) {
313328 tiptapExtensions . push ( ...extension . tiptapExtensions ) ;
314329 }
315330
316- const prosemirrorPlugins =
331+ const priority = getPriority ( extension . key ) ;
332+
333+ const { plugins : prosemirrorPlugins , inputRules } =
317334 this . getProsemirrorPluginsFromExtension ( extension ) ;
318335 // Sometimes a blocknote extension might need to make additional prosemirror plugins, so we generate them here
319336 if ( prosemirrorPlugins . length ) {
320337 tiptapExtensions . push (
321338 TiptapExtension . create ( {
322339 name : extension . key ,
323- priority : getPriority ( extension . key ) ,
340+ priority,
324341 addProseMirrorPlugins : ( ) => prosemirrorPlugins ,
325342 } ) ,
326343 ) ;
327344 }
345+ if ( inputRules . length ) {
346+ if ( ! inputRulesByPriority . has ( priority ) ) {
347+ inputRulesByPriority . set ( priority , [ ] ) ;
348+ }
349+ inputRulesByPriority . get ( priority ) ! . push ( ...inputRules ) ;
350+ }
328351 }
329352
353+ // Collect all input rules into 1 extension to reduce conflicts
354+ tiptapExtensions . push (
355+ TiptapExtension . create ( {
356+ name : "blocknote-input-rules" ,
357+ addProseMirrorPlugins ( ) {
358+ const rules = [ ] as InputRule [ ] ;
359+ inputRulesByPriority
360+ . keys ( )
361+ . toArray ( )
362+ // We sort the rules by their priority (the key)
363+ . sort ( )
364+ . reverse ( )
365+ . forEach ( ( priority ) => {
366+ // Append in reverse priority order
367+ rules . push ( ...inputRulesByPriority . get ( priority ) ! ) ;
368+ } ) ;
369+ return [ inputRulesPlugin ( { rules } ) ] ;
370+ } ,
371+ } ) ,
372+ ) ;
373+
330374 // Add any tiptap extensions from the `_tiptapOptions`
331375 for ( const extension of this . options . _tiptapOptions ?. extensions ?? [ ] ) {
332376 tiptapExtensions . push ( extension ) ;
@@ -341,49 +385,50 @@ export class ExtensionManager {
341385 * - keyboard shortcuts
342386 * - input rules
343387 */
344- private getProsemirrorPluginsFromExtension ( extension : Extension ) : Plugin [ ] {
388+ private getProsemirrorPluginsFromExtension ( extension : Extension ) : {
389+ plugins : Plugin [ ] ;
390+ inputRules : InputRule [ ] ;
391+ } {
392+ const plugins : Plugin [ ] = [ ...( extension . prosemirrorPlugins ?? [ ] ) ] ;
393+ const inputRules : InputRule [ ] = [ ] ;
345394 if (
346395 ! extension . prosemirrorPlugins ?. length &&
347396 ! extension . keyboardShortcuts ?. length &&
348397 ! extension . inputRules ?. length
349398 ) {
350399 // We can bail out early if the extension has no features to add to the tiptap editor
351- return [ ] ;
400+ return { plugins , inputRules } ;
352401 }
353402
354- const plugins : Plugin [ ] = [ ...( extension . prosemirrorPlugins ?? [ ] ) ] ;
355-
356403 this . extensionPlugins . set ( extension , plugins ) ;
357404
358405 if ( extension . inputRules ?. length ) {
359- plugins . push (
360- inputRules ( {
361- rules : extension . inputRules . map ( ( inputRule ) => {
362- return new InputRule ( inputRule . find , ( state , match , start , end ) => {
363- const replaceWith = inputRule . replace ( {
364- match,
365- range : { from : start , to : end } ,
366- editor : this . editor ,
367- } ) ;
368- if ( replaceWith ) {
369- const cursorPosition = this . editor . getTextCursorPosition ( ) ;
370-
371- if (
372- this . editor . schema . blockSchema [ cursorPosition . block . type ]
373- . content !== "inline"
374- ) {
375- return null ;
376- }
377-
378- const blockInfo = getBlockInfoFromTransaction ( state . tr ) ;
379- const tr = state . tr . deleteRange ( start , end ) ;
380-
381- updateBlockTr ( tr , blockInfo . bnBlock . beforePos , replaceWith ) ;
382- return tr ;
383- }
384- return null ;
406+ inputRules . push (
407+ ...extension . inputRules . map ( ( inputRule ) => {
408+ return new InputRule ( inputRule . find , ( state , match , start , end ) => {
409+ const replaceWith = inputRule . replace ( {
410+ match,
411+ range : { from : start , to : end } ,
412+ editor : this . editor ,
385413 } ) ;
386- } ) ,
414+ if ( replaceWith ) {
415+ const cursorPosition = this . editor . getTextCursorPosition ( ) ;
416+
417+ if (
418+ this . editor . schema . blockSchema [ cursorPosition . block . type ]
419+ . content !== "inline"
420+ ) {
421+ return null ;
422+ }
423+
424+ const blockInfo = getBlockInfoFromTransaction ( state . tr ) ;
425+ const tr = state . tr . deleteRange ( start , end ) ;
426+
427+ updateBlockTr ( tr , blockInfo . bnBlock . beforePos , replaceWith ) ;
428+ return tr ;
429+ }
430+ return null ;
431+ } ) ;
387432 } ) ,
388433 ) ;
389434 }
@@ -401,7 +446,7 @@ export class ExtensionManager {
401446 ) ;
402447 }
403448
404- return plugins ;
449+ return { plugins, inputRules } ;
405450 }
406451
407452 /**
0 commit comments