Skip to content

Commit bdd7481

Browse files
committed
Migrate from TipTap extensions to BlockNote native extensions
BlockNote core team confirmed _tiptapOptions will be removed in a future version. Convert both editor extensions (ExternalLinkCapture, ExternalLinkA11y) from TipTap Extension.create() to BlockNote createExtension(), and register via the extensions option instead of _tiptapOptions.extensions. ProseMirror plugin code is unchanged.
1 parent 7d6e8ee commit bdd7481

3 files changed

Lines changed: 58 additions & 64 deletions

File tree

frontend/src/react/components/OpBlockNoteEditor.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,10 @@ export function OpBlockNoteEditor({
105105
},
106106
dictionary: localeDictionary,
107107
...(attachmentsEnabled && { uploadFile }),
108-
_tiptapOptions: {
109-
extensions: [
110-
ExternalLinkA11yExtension,
111-
...(captureExternalLinks ? [ExternalLinkCaptureExtension] : []),
112-
],
113-
},
108+
extensions: [
109+
ExternalLinkA11yExtension,
110+
...(captureExternalLinks ? [ExternalLinkCaptureExtension] : []),
111+
],
114112
};
115113
}, [hocuspocusProvider, doc, activeUser, localeDictionary, attachmentsEnabled, uploadFile, captureExternalLinks]);
116114

frontend/src/react/extensions/external-link-a11y.ts

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@
2626
// See COPYRIGHT and LICENSE files for more details.
2727
//++
2828

29-
import { Extension } from '@tiptap/core';
30-
import { Plugin, PluginKey } from '@tiptap/pm/state';
31-
import { Decoration, DecorationSet } from '@tiptap/pm/view';
32-
import type { Node as PmNode } from '@tiptap/pm/model';
29+
import { createExtension } from '@blocknote/core';
30+
import { Plugin, PluginKey } from 'prosemirror-state';
31+
import { Decoration, DecorationSet } from 'prosemirror-view';
32+
import type { Node as PmNode } from 'prosemirror-model';
3333
import { isHrefExternal } from 'core-stimulus/helpers/external-link-helpers';
3434

3535
const pluginKey = new PluginKey('externalLinkA11y');
@@ -68,30 +68,28 @@ function buildDecorations(doc:PmNode):DecorationSet {
6868
* opens in a new tab. It lives in the main layout (`base.html.erb`) and is
6969
* cloned into the BlockNote shadow DOM by `block-note-element.ts`.
7070
*/
71-
export const ExternalLinkA11yExtension = Extension.create({
72-
name: 'externalLinkA11y',
71+
export const ExternalLinkA11yExtension = createExtension({
72+
key: 'externalLinkA11y',
7373

74-
addProseMirrorPlugins() {
75-
return [
76-
new Plugin({
77-
key: pluginKey,
78-
state: {
79-
init(_, { doc }) {
80-
return buildDecorations(doc);
81-
},
82-
apply(tr, oldDecos) {
83-
if (tr.docChanged) {
84-
return buildDecorations(tr.doc);
85-
}
86-
return oldDecos.map(tr.mapping, tr.doc);
87-
},
74+
prosemirrorPlugins: [
75+
new Plugin({
76+
key: pluginKey,
77+
state: {
78+
init(_, { doc }) {
79+
return buildDecorations(doc);
8880
},
89-
props: {
90-
decorations(state) {
91-
return pluginKey.getState(state) as DecorationSet;
92-
},
81+
apply(tr, oldDecos) {
82+
if (tr.docChanged) {
83+
return buildDecorations(tr.doc);
84+
}
85+
return oldDecos.map(tr.mapping, tr.doc);
9386
},
94-
}),
95-
];
96-
},
87+
},
88+
props: {
89+
decorations(state) {
90+
return pluginKey.getState(state) as DecorationSet;
91+
},
92+
},
93+
}),
94+
],
9795
});

frontend/src/react/extensions/external-link-capture.ts

Lines changed: 29 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626
// See COPYRIGHT and LICENSE files for more details.
2727
//++
2828

29-
import { Extension } from '@tiptap/core';
30-
import { Plugin, PluginKey } from '@tiptap/pm/state';
29+
import { createExtension } from '@blocknote/core';
30+
import { Plugin, PluginKey } from 'prosemirror-state';
3131
import { buildExternalRedirectUrl, isExternalLinkCandidate, isLinkExternal } from 'core-stimulus/helpers/external-link-helpers';
3232

3333
/**
@@ -44,38 +44,36 @@ import { buildExternalRedirectUrl, isExternalLinkCandidate, isLinkExternal } fro
4444
* enabled — when disabled, TipTap's default `openOnClick: true` handles
4545
* link clicks natively.
4646
*/
47-
export const ExternalLinkCaptureExtension = Extension.create({
48-
name: 'externalLinkCapture',
47+
export const ExternalLinkCaptureExtension = createExtension({
48+
key: 'externalLinkCapture',
4949

50-
addProseMirrorPlugins() {
51-
return [
52-
new Plugin({
53-
key: new PluginKey('externalLinkCapture'),
54-
props: {
55-
handleDOMEvents: {
56-
mousedown: (view, event) => {
57-
// Left-click (0) and middle-click (1) only — right-click (2)
58-
// opens the native context menu which reads href from the DOM
59-
// and cannot be intercepted via JavaScript.
60-
if (event.button !== 0 && event.button !== 1) return false;
50+
prosemirrorPlugins: [
51+
new Plugin({
52+
key: new PluginKey('externalLinkCapture'),
53+
props: {
54+
handleDOMEvents: {
55+
mousedown: (view, event) => {
56+
// Left-click (0) and middle-click (1) only — right-click (2)
57+
// opens the native context menu which reads href from the DOM
58+
// and cannot be intercepted via JavaScript.
59+
if (event.button !== 0 && event.button !== 1) return false;
6160

62-
const target = event.target instanceof Element
63-
? event.target
64-
: (event.target as Node)?.parentElement;
65-
const link = target?.closest('a');
66-
if (!(link instanceof HTMLAnchorElement)) return false;
67-
if (!view.dom.contains(link)) return false;
68-
if (!isExternalLinkCandidate(link)) return false;
69-
if (!isLinkExternal(link)) return false;
70-
if (link.dataset.allowExternalLink) return false;
61+
const target = event.target instanceof Element
62+
? event.target
63+
: (event.target as Node)?.parentElement;
64+
const link = target?.closest('a');
65+
if (!(link instanceof HTMLAnchorElement)) return false;
66+
if (!view.dom.contains(link)) return false;
67+
if (!isExternalLinkCandidate(link)) return false;
68+
if (!isLinkExternal(link)) return false;
69+
if (link.dataset.allowExternalLink) return false;
7170

72-
event.preventDefault();
73-
window.open(buildExternalRedirectUrl(link.href), '_blank', 'noopener,noreferrer');
74-
return true;
75-
},
71+
event.preventDefault();
72+
window.open(buildExternalRedirectUrl(link.href), '_blank', 'noopener,noreferrer');
73+
return true;
7674
},
7775
},
78-
}),
79-
];
80-
},
76+
},
77+
}),
78+
],
8179
});

0 commit comments

Comments
 (0)