Skip to content

Commit c9d6416

Browse files
committed
fix: use a single prosemirror plugin instance for all inputRules
1 parent 17a5905 commit c9d6416

File tree

1 file changed

+83
-38
lines changed
  • packages/core/src/editor/managers/ExtensionManager

1 file changed

+83
-38
lines changed

packages/core/src/editor/managers/ExtensionManager/index.ts

Lines changed: 83 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { InputRule, inputRules } from "@handlewithcare/prosemirror-inputrules";
1+
import {
2+
InputRule,
3+
inputRules as inputRulesPlugin,
4+
} from "@handlewithcare/prosemirror-inputrules";
25
import {
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

Comments
 (0)