Skip to content

Commit bb1c1a3

Browse files
authored
fix: replace plugin reordering workaround with simpler heading input rule override (#1158)
1 parent 13c0d99 commit bb1c1a3

File tree

3 files changed

+42
-29
lines changed

3 files changed

+42
-29
lines changed

src/components/typist-editor.tsx

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { getMarkdownSerializerInstance } from '../serializers/markdown/markdown'
1414
import { getAllNodesAttributesByType, resolveContentSelection } from './typist-editor.helper'
1515

1616
import type { Editor as CoreEditor, EditorEvents, Extensions } from '@tiptap/core'
17-
import type { Plugin, Selection } from '@tiptap/pm/state'
17+
import type { Selection } from '@tiptap/pm/state'
1818

1919
/**
2020
* The forwarded ref that describes the helper methods that the `TypistEditor` parent component
@@ -315,30 +315,6 @@ const TypistEditor = forwardRef<TypistEditorRef, TypistEditorProps>(function Typ
315315
)
316316
}
317317

318-
// Move the suggestion plugins to the top of the plugins list so they have a higher priority
319-
// than all input rules (such as the ones used for Markdown shortcuts)
320-
// ref: https://github.com/ueberdosis/tiptap/issues/2570
321-
if (view.state.plugins.length > 0) {
322-
const restOfPlugins: Plugin[] = []
323-
const suggestionPlugins: Plugin[] = []
324-
325-
view.state.plugins.forEach((plugin) => {
326-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
327-
// @ts-ignore: The `Plugin` type does not include `key`
328-
if ((plugin.key as string).includes('Suggestion')) {
329-
suggestionPlugins.push(plugin)
330-
} else {
331-
restOfPlugins.push(plugin)
332-
}
333-
})
334-
335-
view.updateState(
336-
view.state.reconfigure({
337-
plugins: [...suggestionPlugins, ...restOfPlugins],
338-
}),
339-
)
340-
}
341-
342318
// Invoke the user `onCreate` handle after all internal initializations
343319
onCreate?.(props)
344320
},
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { Heading } from '@tiptap/extension-heading'
2+
import { textblockTypeInputRule } from '@tiptap/react'
3+
4+
import type { HeadingOptions } from '@tiptap/extension-heading'
5+
6+
/**
7+
* The options available to customize the `RichTextHeading` extension.
8+
*/
9+
type RichTextHeadingOptions = HeadingOptions
10+
11+
/**
12+
* Custom extension that extends the built-in `Heading` extension to override the input rule for
13+
* headings to trigger only on space (e.g., `## `), preventing conflicts with suggestion extensions
14+
* that use `#` as their trigger character.
15+
*
16+
* This was properly fixed in Tiptap v3 where input rules respect extension priorities, making this
17+
* extension likely unnecessary after migrating.
18+
*
19+
* @see https://github.com/ueberdosis/tiptap/issues/2570
20+
* @see https://github.com/ueberdosis/tiptap/pull/6832
21+
*/
22+
const RichTextHeading = Heading.extend<RichTextHeadingOptions>({
23+
addInputRules() {
24+
return this.options.levels.map((level: number) => {
25+
return textblockTypeInputRule({
26+
find: new RegExp(`^(#{1,${level}}) $`),
27+
type: this.type,
28+
getAttributes: {
29+
level,
30+
},
31+
})
32+
})
33+
},
34+
})
35+
36+
export { RichTextHeading }
37+
38+
export type { RichTextHeadingOptions }

src/extensions/rich-text/rich-text-kit.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { CodeBlock } from '@tiptap/extension-code-block'
55
import { Dropcursor } from '@tiptap/extension-dropcursor'
66
import { Gapcursor } from '@tiptap/extension-gapcursor'
77
import { HardBreak } from '@tiptap/extension-hard-break'
8-
import { Heading } from '@tiptap/extension-heading'
98
import { History } from '@tiptap/extension-history'
109
import { HorizontalRule } from '@tiptap/extension-horizontal-rule'
1110
import { Italic } from '@tiptap/extension-italic'
@@ -27,6 +26,7 @@ import { PasteMarkdown } from './paste-markdown'
2726
import { RichTextBulletList } from './rich-text-bullet-list'
2827
import { RichTextCode } from './rich-text-code'
2928
import { RichTextDocument } from './rich-text-document'
29+
import { RichTextHeading, RichTextHeadingOptions } from './rich-text-heading'
3030
import { RichTextImage } from './rich-text-image'
3131
import { RichTextLink } from './rich-text-link'
3232
import { RichTextOrderedList } from './rich-text-ordered-list'
@@ -39,7 +39,6 @@ import type { CodeOptions } from '@tiptap/extension-code'
3939
import type { CodeBlockOptions } from '@tiptap/extension-code-block'
4040
import type { DropcursorOptions } from '@tiptap/extension-dropcursor'
4141
import type { HardBreakOptions } from '@tiptap/extension-hard-break'
42-
import type { HeadingOptions } from '@tiptap/extension-heading'
4342
import type { HistoryOptions } from '@tiptap/extension-history'
4443
import type { HorizontalRuleOptions } from '@tiptap/extension-horizontal-rule'
4544
import type { ItalicOptions } from '@tiptap/extension-italic'
@@ -105,7 +104,7 @@ type RichTextKitOptions = {
105104
/**
106105
* Set options for the `Heading` extension, or `false` to disable.
107106
*/
108-
heading: Partial<HeadingOptions> | false
107+
heading: Partial<RichTextHeadingOptions> | false
109108

110109
/**
111110
* Set options for the `History` extension, or `false` to disable.
@@ -275,7 +274,7 @@ const RichTextKit = Extension.create<RichTextKitOptions>({
275274
}
276275

277276
if (this.options.heading !== false) {
278-
extensions.push(Heading.configure(this.options?.heading))
277+
extensions.push(RichTextHeading.configure(this.options?.heading))
279278
}
280279

281280
if (this.options.history !== false) {

0 commit comments

Comments
 (0)