diff --git a/demos/includeDependencies.txt b/demos/includeDependencies.txt
index ec9d28a934..342c8ed59d 100644
--- a/demos/includeDependencies.txt
+++ b/demos/includeDependencies.txt
@@ -5,7 +5,6 @@ prosemirror-commands
prosemirror-dropcursor
prosemirror-gapcursor
prosemirror-history
-prosemirror-inputrules
prosemirror-keymap
prosemirror-model
prosemirror-schema-list
diff --git a/demos/src/Marks/Link/Vue/index.vue b/demos/src/Marks/Link/Vue/index.vue
index e604e00629..a9a5fffd26 100644
--- a/demos/src/Marks/Link/Vue/index.vue
+++ b/demos/src/Marks/Link/Vue/index.vue
@@ -16,7 +16,6 @@ import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
import Link from '@tiptap/extension-link'
-import Bold from '@tiptap/extension-bold'
export default {
components: {
@@ -35,14 +34,13 @@ export default {
Document,
Paragraph,
Text,
- Bold,
Link.configure({
openOnClick: false,
}),
],
content: `
- Wow, this editor has support for links to the whole world wide web. We tested a lot of URLs and I think you can add *every URL* you want. Isn’t that cool? Let’s try another one! Yep, seems to work.
+ Wow, this editor has support for links to the whole world wide web. We tested a lot of URLs and I think you can add *every URL* you want. Isn’t that cool? Let’s try another one! Yep, seems to work.
By default every link will get a \`rel="noopener noreferrer nofollow"\` attribute. It’s configurable though.
diff --git a/demos/src/Nodes/CodeBlock/Vue/index.spec.js b/demos/src/Nodes/CodeBlock/Vue/index.spec.js
index 9f202ff7bd..0c626edd94 100644
--- a/demos/src/Nodes/CodeBlock/Vue/index.spec.js
+++ b/demos/src/Nodes/CodeBlock/Vue/index.spec.js
@@ -126,6 +126,17 @@ context('/src/Nodes/CodeBlock/Vue/', () => {
})
})
+ it('should make a code block from backtick markdown shortcuts followed by enter', () => {
+ cy.get('.ProseMirror').then(([{ editor }]) => {
+ editor.commands.clearContent()
+
+ cy.get('.ProseMirror')
+ .type('```{enter}Code')
+ .find('pre>code')
+ .should('contain', 'Code')
+ })
+ })
+
it('reverts the markdown shortcut when pressing backspace', () => {
cy.get('.ProseMirror').then(([{ editor }]) => {
editor.commands.clearContent()
diff --git a/packages/core/package.json b/packages/core/package.json
index f355098143..b75e397c11 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -25,7 +25,6 @@
],
"dependencies": {
"@types/prosemirror-commands": "^1.0.4",
- "@types/prosemirror-inputrules": "^1.0.4",
"@types/prosemirror-keymap": "^1.0.4",
"@types/prosemirror-model": "^1.13.2",
"@types/prosemirror-schema-list": "^1.0.3",
@@ -33,7 +32,6 @@
"@types/prosemirror-transform": "^1.1.4",
"@types/prosemirror-view": "^1.19.1",
"prosemirror-commands": "^1.1.11",
- "prosemirror-inputrules": "^1.1.3",
"prosemirror-keymap": "^1.1.3",
"prosemirror-model": "^1.14.3",
"prosemirror-schema-list": "^1.1.6",
diff --git a/packages/core/src/CommandManager.ts b/packages/core/src/CommandManager.ts
index 6a19e7a830..cff6c00a87 100644
--- a/packages/core/src/CommandManager.ts
+++ b/packages/core/src/CommandManager.ts
@@ -1,5 +1,6 @@
-import { EditorState, Transaction } from 'prosemirror-state'
+import { Transaction } from 'prosemirror-state'
import { Editor } from './Editor'
+import createChainableState from './helpers/createChainableState'
import {
SingleCommands,
ChainedCommands,
@@ -106,7 +107,10 @@ export default class CommandManager {
tr,
editor,
view,
- state: this.chainableState(tr, state),
+ state: createChainableState({
+ state,
+ transaction: tr,
+ }),
dispatch: shouldDispatch
? () => undefined
: undefined,
@@ -124,36 +128,4 @@ export default class CommandManager {
return props
}
- public chainableState(tr: Transaction, state: EditorState): EditorState {
- let { selection } = tr
- let { doc } = tr
- let { storedMarks } = tr
-
- return {
- ...state,
- schema: state.schema,
- plugins: state.plugins,
- apply: state.apply.bind(state),
- applyTransaction: state.applyTransaction.bind(state),
- reconfigure: state.reconfigure.bind(state),
- toJSON: state.toJSON.bind(state),
- get storedMarks() {
- return storedMarks
- },
- get selection() {
- return selection
- },
- get doc() {
- return doc
- },
- get tr() {
- selection = tr.selection
- doc = tr.doc
- storedMarks = tr.storedMarks
-
- return tr
- },
- }
- }
-
}
diff --git a/packages/core/src/Editor.ts b/packages/core/src/Editor.ts
index dfa947dce2..1af4279514 100644
--- a/packages/core/src/Editor.ts
+++ b/packages/core/src/Editor.ts
@@ -15,6 +15,7 @@ import getText from './helpers/getText'
import isNodeEmpty from './helpers/isNodeEmpty'
import getTextSeralizersFromSchema from './helpers/getTextSeralizersFromSchema'
import createStyleTag from './utilities/createStyleTag'
+import isFunction from './utilities/isFunction'
import CommandManager from './CommandManager'
import ExtensionManager from './ExtensionManager'
import EventEmitter from './EventEmitter'
@@ -184,7 +185,7 @@ export class Editor extends EventEmitter {
* @param handlePlugins Control how to merge the plugin into the existing plugins.
*/
public registerPlugin(plugin: Plugin, handlePlugins?: (newPlugin: Plugin, plugins: Plugin[]) => Plugin[]): void {
- const plugins = typeof handlePlugins === 'function'
+ const plugins = isFunction(handlePlugins)
? handlePlugins(plugin, this.state.plugins)
: [...this.state.plugins, plugin]
diff --git a/packages/core/src/Extension.ts b/packages/core/src/Extension.ts
index e30fe36c66..55ce6f21bc 100644
--- a/packages/core/src/Extension.ts
+++ b/packages/core/src/Extension.ts
@@ -1,5 +1,6 @@
import { Plugin, Transaction } from 'prosemirror-state'
-import { InputRule } from 'prosemirror-inputrules'
+import { InputRule } from './InputRule'
+import { PasteRule } from './PasteRule'
import { Editor } from './Editor'
import { Node } from './Node'
import { Mark } from './Mark'
@@ -81,7 +82,7 @@ declare module '@tiptap/core' {
options: Options,
editor: Editor,
parent: ParentConfig>['addPasteRules'],
- }) => Plugin[],
+ }) => PasteRule[],
/**
* ProseMirror plugins
diff --git a/packages/core/src/ExtensionManager.ts b/packages/core/src/ExtensionManager.ts
index 53d861cf25..66c20c4e85 100644
--- a/packages/core/src/ExtensionManager.ts
+++ b/packages/core/src/ExtensionManager.ts
@@ -1,6 +1,7 @@
import { keymap } from 'prosemirror-keymap'
import { Schema, Node as ProsemirrorNode } from 'prosemirror-model'
-import { inputRules as inputRulesPlugin } from 'prosemirror-inputrules'
+import { inputRulesPlugin } from './InputRule'
+import { pasteRulesPlugin } from './PasteRule'
import { EditorView, Decoration } from 'prosemirror-view'
import { Plugin } from 'prosemirror-state'
import { Editor } from './Editor'
@@ -210,7 +211,12 @@ export default class ExtensionManager {
// so it feels more natural to run plugins at the end of an array first.
// That’s why we have to reverse the `extensions` array and sort again
// based on the `priority` option.
- return ExtensionManager.sort([...this.extensions].reverse())
+ const extensions = ExtensionManager.sort([...this.extensions].reverse())
+
+ const inputRules: any[] = []
+ const pasteRules: any[] = []
+
+ const allPlugins = extensions
.map(extension => {
const context = {
name: extension.name,
@@ -248,12 +254,7 @@ export default class ExtensionManager {
)
if (this.editor.options.enableInputRules && addInputRules) {
- const inputRules = addInputRules()
- const inputRulePlugins = inputRules.length
- ? [inputRulesPlugin({ rules: inputRules })]
- : []
-
- plugins.push(...inputRulePlugins)
+ inputRules.push(...addInputRules())
}
const addPasteRules = getExtensionField(
@@ -263,9 +264,7 @@ export default class ExtensionManager {
)
if (this.editor.options.enablePasteRules && addPasteRules) {
- const pasteRulePlugins = addPasteRules()
-
- plugins.push(...pasteRulePlugins)
+ pasteRules.push(...addPasteRules())
}
const addProseMirrorPlugins = getExtensionField(
@@ -283,6 +282,12 @@ export default class ExtensionManager {
return plugins
})
.flat()
+
+ return [
+ inputRulesPlugin(inputRules),
+ pasteRulesPlugin(pasteRules),
+ ...allPlugins,
+ ]
}
get attributes() {
diff --git a/packages/core/src/InputRule.ts b/packages/core/src/InputRule.ts
new file mode 100644
index 0000000000..0f6933e093
--- /dev/null
+++ b/packages/core/src/InputRule.ts
@@ -0,0 +1,245 @@
+import { EditorView } from 'prosemirror-view'
+import { EditorState, Plugin, TextSelection } from 'prosemirror-state'
+import createChainableState from './helpers/createChainableState'
+import isRegExp from './utilities/isRegExp'
+import { Range, ExtendedRegExpMatchArray } from './types'
+
+export type InputRuleMatch = {
+ index: number,
+ text: string,
+ replaceWith?: string,
+ match?: RegExpMatchArray,
+ data?: Record,
+}
+
+export type InputRuleFinder =
+ | RegExp
+ | ((text: string) => InputRuleMatch | null)
+
+export class InputRule {
+ find: InputRuleFinder
+
+ handler: (props: {
+ state: EditorState,
+ range: Range,
+ match: ExtendedRegExpMatchArray,
+ }) => void
+
+ constructor(config: {
+ find: InputRuleFinder,
+ handler: (props: {
+ state: EditorState,
+ range: Range,
+ match: ExtendedRegExpMatchArray,
+ }) => void,
+ }) {
+ this.find = config.find
+ this.handler = config.handler
+ }
+}
+
+const inputRuleMatcherHandler = (text: string, find: InputRuleFinder): ExtendedRegExpMatchArray | null => {
+ if (isRegExp(find)) {
+ return find.exec(text)
+ }
+
+ const inputRuleMatch = find(text)
+
+ if (!inputRuleMatch) {
+ return null
+ }
+
+ const result: ExtendedRegExpMatchArray = []
+
+ result.push(inputRuleMatch.text)
+ result.index = inputRuleMatch.index
+ result.input = text
+ result.data = inputRuleMatch.data
+
+ if (inputRuleMatch.replaceWith) {
+ if (!inputRuleMatch.text.includes(inputRuleMatch.replaceWith)) {
+ console.warn('[tiptap warn]: "inputRuleMatch.replaceWith" must be part of "inputRuleMatch.text".')
+ }
+
+ result.push(inputRuleMatch.replaceWith)
+ }
+
+ return result
+}
+
+function run(config: {
+ view: EditorView,
+ from: number,
+ to: number,
+ text: string,
+ rules: InputRule[],
+ plugin: Plugin,
+}): any {
+ const {
+ view,
+ from,
+ to,
+ text,
+ rules,
+ plugin,
+ } = config
+
+ if (view.composing) {
+ return false
+ }
+
+ const $from = view.state.doc.resolve(from)
+
+ if (
+ // check for code node
+ $from.parent.type.spec.code
+ // check for code mark
+ || !!($from.nodeBefore || $from.nodeAfter)?.marks.find(mark => mark.type.spec.code)
+ ) {
+ return false
+ }
+
+ let matched = false
+ const maxMatch = 500
+ const textBefore = $from.parent.textBetween(
+ Math.max(0, $from.parentOffset - maxMatch),
+ $from.parentOffset,
+ undefined,
+ '\ufffc',
+ ) + text
+
+ rules.forEach(rule => {
+ if (matched) {
+ return
+ }
+
+ const match = inputRuleMatcherHandler(textBefore, rule.find)
+
+ if (!match) {
+ return
+ }
+
+ const tr = view.state.tr
+ const state = createChainableState({
+ state: view.state,
+ transaction: tr,
+ })
+ const range = {
+ from: from - (match[0].length - text.length),
+ to,
+ }
+
+ rule.handler({
+ state,
+ range,
+ match,
+ })
+
+ // stop if there are no changes
+ if (!tr.steps.length) {
+ return
+ }
+
+ // store transform as meta data
+ // so we can undo input rules within the `undoInputRules` command
+ tr.setMeta(plugin, {
+ transform: tr,
+ from,
+ to,
+ text,
+ })
+
+ view.dispatch(tr)
+ matched = true
+ })
+
+ return matched
+}
+
+/**
+ * Create an input rules plugin. When enabled, it will cause text
+ * input that matches any of the given rules to trigger the rule’s
+ * action.
+ */
+export function inputRulesPlugin(rules: InputRule[]): Plugin {
+ const plugin = new Plugin({
+ state: {
+ init() {
+ return null
+ },
+ apply(tr, prev) {
+ const stored = tr.getMeta(this)
+
+ if (stored) {
+ return stored
+ }
+
+ return tr.selectionSet || tr.docChanged
+ ? null
+ : prev
+ },
+ },
+
+ props: {
+ handleTextInput(view, from, to, text) {
+ return run({
+ view,
+ from,
+ to,
+ text,
+ rules,
+ plugin,
+ })
+ },
+
+ handleDOMEvents: {
+ compositionend: view => {
+ setTimeout(() => {
+ const { $cursor } = view.state.selection as TextSelection
+
+ if ($cursor) {
+ run({
+ view,
+ from: $cursor.pos,
+ to: $cursor.pos,
+ text: '',
+ rules,
+ plugin,
+ })
+ }
+ })
+
+ return false
+ },
+ },
+
+ // add support for input rules to trigger on enter
+ // this is useful for example for code blocks
+ handleKeyDown(view, event) {
+ if (event.key !== 'Enter') {
+ return false
+ }
+
+ const { $cursor } = view.state.selection as TextSelection
+
+ if ($cursor) {
+ return run({
+ view,
+ from: $cursor.pos,
+ to: $cursor.pos,
+ text: '\n',
+ rules,
+ plugin,
+ })
+ }
+
+ return false
+ },
+ },
+
+ // @ts-ignore
+ isInputRules: true,
+ }) as Plugin
+
+ return plugin
+}
diff --git a/packages/core/src/Mark.ts b/packages/core/src/Mark.ts
index 02dc59b949..8c8a2cbf7f 100644
--- a/packages/core/src/Mark.ts
+++ b/packages/core/src/Mark.ts
@@ -5,7 +5,8 @@ import {
MarkType,
} from 'prosemirror-model'
import { Plugin, Transaction } from 'prosemirror-state'
-import { InputRule } from 'prosemirror-inputrules'
+import { InputRule } from './InputRule'
+import { PasteRule } from './PasteRule'
import mergeDeep from './utilities/mergeDeep'
import {
Extensions,
@@ -91,7 +92,7 @@ declare module '@tiptap/core' {
editor: Editor,
type: MarkType,
parent: ParentConfig>['addPasteRules'],
- }) => Plugin[],
+ }) => PasteRule[],
/**
* ProseMirror plugins
@@ -281,6 +282,15 @@ declare module '@tiptap/core' {
parent: ParentConfig>['spanning'],
}) => MarkSpec['spanning']),
+ /**
+ * Code
+ */
+ code?: boolean | ((this: {
+ name: string,
+ options: Options,
+ parent: ParentConfig>['code'],
+ }) => boolean),
+
/**
* Parse HTML
*/
diff --git a/packages/core/src/Node.ts b/packages/core/src/Node.ts
index ce90159210..ec0cfd8aaf 100644
--- a/packages/core/src/Node.ts
+++ b/packages/core/src/Node.ts
@@ -5,7 +5,8 @@ import {
NodeType,
} from 'prosemirror-model'
import { Plugin, Transaction } from 'prosemirror-state'
-import { InputRule } from 'prosemirror-inputrules'
+import { InputRule } from './InputRule'
+import { PasteRule } from './PasteRule'
import mergeDeep from './utilities/mergeDeep'
import {
Extensions,
@@ -91,7 +92,7 @@ declare module '@tiptap/core' {
editor: Editor,
type: NodeType,
parent: ParentConfig>['addPasteRules'],
- }) => Plugin[],
+ }) => PasteRule[],
/**
* ProseMirror plugins
diff --git a/packages/core/src/PasteRule.ts b/packages/core/src/PasteRule.ts
new file mode 100644
index 0000000000..13e16b67de
--- /dev/null
+++ b/packages/core/src/PasteRule.ts
@@ -0,0 +1,177 @@
+import { EditorState, Plugin } from 'prosemirror-state'
+import createChainableState from './helpers/createChainableState'
+import isRegExp from './utilities/isRegExp'
+import { Range, ExtendedRegExpMatchArray } from './types'
+
+export type PasteRuleMatch = {
+ index: number,
+ text: string,
+ replaceWith?: string,
+ match?: RegExpMatchArray,
+ data?: Record,
+}
+
+export type PasteRuleFinder =
+ | RegExp
+ | ((text: string) => PasteRuleMatch[] | null | undefined)
+
+export class PasteRule {
+ find: PasteRuleFinder
+
+ handler: (props: {
+ state: EditorState,
+ range: Range,
+ match: ExtendedRegExpMatchArray,
+ }) => void
+
+ constructor(config: {
+ find: PasteRuleFinder,
+ handler: (props: {
+ state: EditorState,
+ range: Range,
+ match: ExtendedRegExpMatchArray,
+ }) => void,
+ }) {
+ this.find = config.find
+ this.handler = config.handler
+ }
+}
+
+const pasteRuleMatcherHandler = (text: string, find: PasteRuleFinder): ExtendedRegExpMatchArray[] => {
+ if (isRegExp(find)) {
+ return [...text.matchAll(find)]
+ }
+
+ const matches = find(text)
+
+ if (!matches) {
+ return []
+ }
+
+ return matches.map(pasteRuleMatch => {
+ const result: ExtendedRegExpMatchArray = []
+
+ result.push(pasteRuleMatch.text)
+ result.index = pasteRuleMatch.index
+ result.input = text
+ result.data = pasteRuleMatch.data
+
+ if (pasteRuleMatch.replaceWith) {
+ if (!pasteRuleMatch.text.includes(pasteRuleMatch.replaceWith)) {
+ console.warn('[tiptap warn]: "pasteRuleMatch.replaceWith" must be part of "pasteRuleMatch.text".')
+ }
+
+ result.push(pasteRuleMatch.replaceWith)
+ }
+
+ return result
+ })
+}
+
+function run(config: {
+ state: EditorState,
+ from: number,
+ to: number,
+ rules: PasteRule[],
+ plugin: Plugin,
+}): any {
+ const {
+ state,
+ from,
+ to,
+ rules,
+ } = config
+
+ state.doc.nodesBetween(from, to, (node, pos) => {
+ if (!node.isTextblock || node.type.spec.code) {
+ return
+ }
+
+ const resolvedFrom = Math.max(from, pos)
+ const resolvedTo = Math.min(to, pos + node.content.size)
+ const textToMatch = node.textBetween(
+ resolvedFrom - pos,
+ resolvedTo - pos,
+ undefined,
+ '\ufffc',
+ )
+
+ rules.forEach(rule => {
+ const matches = pasteRuleMatcherHandler(textToMatch, rule.find)
+
+ matches.forEach(match => {
+ if (match.index === undefined) {
+ return
+ }
+
+ const start = resolvedFrom + match.index
+ const end = start + match[0].length
+ const range = {
+ from: state.tr.mapping.map(start),
+ to: state.tr.mapping.map(end),
+ }
+
+ rule.handler({
+ state,
+ range,
+ match,
+ })
+ })
+ })
+ }, from)
+}
+
+/**
+ * Create an paste rules plugin. When enabled, it will cause pasted
+ * text that matches any of the given rules to trigger the rule’s
+ * action.
+ */
+export function pasteRulesPlugin(rules: PasteRule[]): Plugin {
+ const plugin = new Plugin({
+ appendTransaction: (transactions, oldState, state) => {
+ const transaction = transactions[0]
+
+ // stop if there is not a paste event
+ if (!transaction.getMeta('paste')) {
+ return
+ }
+
+ // stop if there is no changed range
+ const { doc, before } = transaction
+ const from = before.content.findDiffStart(doc.content)
+ const to = before.content.findDiffEnd(doc.content)
+
+ if (!from || !to) {
+ return
+ }
+
+ // build a chainable state
+ // so we can use a single transaction for all paste rules
+ const tr = state.tr
+ const chainableState = createChainableState({
+ state,
+ transaction: tr,
+ })
+
+ run({
+ state: chainableState,
+ from,
+ to: to.b,
+ rules,
+ plugin,
+ })
+
+ // stop if there are no changes
+ if (!tr.steps.length) {
+ return
+ }
+
+ return tr
+ },
+
+ // @ts-ignore
+ isPasteRules: true,
+ })
+
+ return plugin
+}
diff --git a/packages/core/src/commands/undoInputRule.ts b/packages/core/src/commands/undoInputRule.ts
index f7a7f602a9..6d9ca42688 100644
--- a/packages/core/src/commands/undoInputRule.ts
+++ b/packages/core/src/commands/undoInputRule.ts
@@ -1,4 +1,3 @@
-import { undoInputRule as originalUndoInputRule } from 'prosemirror-inputrules'
import { RawCommands } from '../types'
declare module '@tiptap/core' {
@@ -13,5 +12,34 @@ declare module '@tiptap/core' {
}
export const undoInputRule: RawCommands['undoInputRule'] = () => ({ state, dispatch }) => {
- return originalUndoInputRule(state, dispatch)
+ const plugins = state.plugins
+
+ for (let i = 0; i < plugins.length; i += 1) {
+ const plugin = plugins[i]
+ let undoable
+
+ // @ts-ignore
+ // eslint-disable-next-line
+ if (plugin.spec.isInputRules && (undoable = plugin.getState(state))) {
+ if (dispatch) {
+ const tr = state.tr
+ const toUndo = undoable.transform
+
+ for (let j = toUndo.steps.length - 1; j >= 0; j -= 1) {
+ tr.step(toUndo.steps[j].invert(toUndo.docs[j]))
+ }
+
+ if (undoable.text) {
+ const marks = tr.doc.resolve(undoable.from).marks()
+ tr.replaceWith(undoable.from, undoable.to, state.schema.text(undoable.text, marks))
+ } else {
+ tr.delete(undoable.from, undoable.to)
+ }
+ }
+
+ return true
+ }
+ }
+
+ return false
}
diff --git a/packages/core/src/helpers/createChainableState.ts b/packages/core/src/helpers/createChainableState.ts
new file mode 100644
index 0000000000..7e6a668c60
--- /dev/null
+++ b/packages/core/src/helpers/createChainableState.ts
@@ -0,0 +1,37 @@
+import { EditorState, Transaction } from 'prosemirror-state'
+
+export default function createChainableState(config: {
+ transaction: Transaction,
+ state: EditorState,
+}): EditorState {
+ const { state, transaction } = config
+ let { selection } = transaction
+ let { doc } = transaction
+ let { storedMarks } = transaction
+
+ return {
+ ...state,
+ schema: state.schema,
+ plugins: state.plugins,
+ apply: state.apply.bind(state),
+ applyTransaction: state.applyTransaction.bind(state),
+ reconfigure: state.reconfigure.bind(state),
+ toJSON: state.toJSON.bind(state),
+ get storedMarks() {
+ return storedMarks
+ },
+ get selection() {
+ return selection
+ },
+ get doc() {
+ return doc
+ },
+ get tr() {
+ selection = transaction.selection
+ doc = transaction.doc
+ storedMarks = transaction.storedMarks
+
+ return transaction
+ },
+ }
+}
diff --git a/packages/core/src/helpers/getSchemaByResolvedExtensions.ts b/packages/core/src/helpers/getSchemaByResolvedExtensions.ts
index fb1d9f21ba..2ef0063964 100644
--- a/packages/core/src/helpers/getSchemaByResolvedExtensions.ts
+++ b/packages/core/src/helpers/getSchemaByResolvedExtensions.ts
@@ -108,10 +108,11 @@ export default function getSchemaByResolvedExtensions(extensions: Extensions): S
const schema: MarkSpec = cleanUpSchemaItem({
...extraMarkFields,
- inclusive: callOrReturn(getExtensionField(extension, 'inclusive', context)),
- excludes: callOrReturn(getExtensionField(extension, 'excludes', context)),
- group: callOrReturn(getExtensionField(extension, 'group', context)),
- spanning: callOrReturn(getExtensionField(extension, 'spanning', context)),
+ inclusive: callOrReturn(getExtensionField(extension, 'inclusive', context)),
+ excludes: callOrReturn(getExtensionField(extension, 'excludes', context)),
+ group: callOrReturn(getExtensionField(extension, 'group', context)),
+ spanning: callOrReturn(getExtensionField(extension, 'spanning', context)),
+ code: callOrReturn(getExtensionField(extension, 'code', context)),
attrs: Object.fromEntries(extensionAttributes.map(extensionAttribute => {
return [extensionAttribute.name, { default: extensionAttribute?.attribute?.default }]
})),
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index fbb4f12fe7..424a28b294 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -7,11 +7,17 @@ export * from './Node'
export * from './Mark'
export * from './NodeView'
export * from './Tracker'
+export * from './InputRule'
+export * from './PasteRule'
export * from './types'
export { default as nodeInputRule } from './inputRules/nodeInputRule'
export { default as markInputRule } from './inputRules/markInputRule'
+export { default as textblockTypeInputRule } from './inputRules/textblockTypeInputRule'
+export { default as textInputRule } from './inputRules/textInputRule'
+export { default as wrappingInputRule } from './inputRules/wrappingInputRule'
export { default as markPasteRule } from './pasteRules/markPasteRule'
+export { default as textPasteRule } from './pasteRules/textPasteRule'
export { default as callOrReturn } from './utilities/callOrReturn'
export { default as mergeAttributes } from './utilities/mergeAttributes'
diff --git a/packages/core/src/inputRules/markInputRule.ts b/packages/core/src/inputRules/markInputRule.ts
index 516951caff..9e5c8e5aa6 100644
--- a/packages/core/src/inputRules/markInputRule.ts
+++ b/packages/core/src/inputRules/markInputRule.ts
@@ -1,50 +1,70 @@
-import { InputRule } from 'prosemirror-inputrules'
+import { InputRule, InputRuleFinder } from '../InputRule'
import { MarkType } from 'prosemirror-model'
import getMarksBetween from '../helpers/getMarksBetween'
+import callOrReturn from '../utilities/callOrReturn'
+import { ExtendedRegExpMatchArray } from '../types'
-export default function (regexp: RegExp, markType: MarkType, getAttributes?: Function): InputRule {
- return new InputRule(regexp, (state, match, start, end) => {
- const attributes = getAttributes instanceof Function
- ? getAttributes(match)
- : getAttributes
- const { tr } = state
- const captureGroup = match[match.length - 1]
- const fullMatch = match[0]
- let markEnd = end
-
- if (captureGroup) {
- const startSpaces = fullMatch.search(/\S/)
- const textStart = start + fullMatch.indexOf(captureGroup)
- const textEnd = textStart + captureGroup.length
-
- const excludedMarks = getMarksBetween(start, end, state)
- .filter(item => {
- // TODO: PR to add excluded to MarkType
- // @ts-ignore
- const { excluded } = item.mark.type
- return excluded.find((type: MarkType) => type.name === markType.name)
- })
- .filter(item => item.to > textStart)
-
- if (excludedMarks.length) {
- return null
- }
+/**
+ * Build an input rule that adds a mark when the
+ * matched text is typed into it.
+ */
+export default function markInputRule(config: {
+ find: InputRuleFinder,
+ type: MarkType,
+ getAttributes?:
+ | Record
+ | ((match: ExtendedRegExpMatchArray) => Record)
+ | false
+ | null
+ ,
+}) {
+ return new InputRule({
+ find: config.find,
+ handler: ({ state, range, match }) => {
+ const attributes = callOrReturn(config.getAttributes, undefined, match)
- if (textEnd < end) {
- tr.delete(textEnd, end)
+ if (attributes === false || attributes === null) {
+ return
}
- if (textStart > start) {
- tr.delete(start + startSpaces, textStart)
- }
+ const { tr } = state
+ const captureGroup = match[match.length - 1]
+ const fullMatch = match[0]
+ let markEnd = range.to
+
+ if (captureGroup) {
+ const startSpaces = fullMatch.search(/\S/)
+ const textStart = range.from + fullMatch.indexOf(captureGroup)
+ const textEnd = textStart + captureGroup.length
+
+ const excludedMarks = getMarksBetween(range.from, range.to, state)
+ .filter(item => {
+ // TODO: PR to add excluded to MarkType
+ // @ts-ignore
+ const { excluded } = item.mark.type
- markEnd = start + startSpaces + captureGroup.length
+ return excluded.find((type: MarkType) => type.name === config.type.name)
+ })
+ .filter(item => item.to > textStart)
- tr.addMark(start + startSpaces, markEnd, markType.create(attributes))
+ if (excludedMarks.length) {
+ return null
+ }
- tr.removeStoredMark(markType)
- }
+ if (textEnd < range.to) {
+ tr.delete(textEnd, range.to)
+ }
- return tr
+ if (textStart > range.from) {
+ tr.delete(range.from + startSpaces, textStart)
+ }
+
+ markEnd = range.from + startSpaces + captureGroup.length
+
+ tr.addMark(range.from + startSpaces, markEnd, config.type.create(attributes || {}))
+
+ tr.removeStoredMark(config.type)
+ }
+ },
})
}
diff --git a/packages/core/src/inputRules/nodeInputRule.ts b/packages/core/src/inputRules/nodeInputRule.ts
index 49ab3543c0..3b6b410c91 100644
--- a/packages/core/src/inputRules/nodeInputRule.ts
+++ b/packages/core/src/inputRules/nodeInputRule.ts
@@ -1,32 +1,49 @@
-import { InputRule } from 'prosemirror-inputrules'
import { NodeType } from 'prosemirror-model'
+import { InputRule, InputRuleFinder } from '../InputRule'
+import { ExtendedRegExpMatchArray } from '../types'
+import callOrReturn from '../utilities/callOrReturn'
-export default function (regexp: RegExp, type: NodeType, getAttributes?: (match: any) => any): InputRule {
- return new InputRule(regexp, (state, match, start, end) => {
- const attributes = getAttributes instanceof Function
- ? getAttributes(match)
- : getAttributes
- const { tr } = state
+/**
+ * Build an input rule that adds a node when the
+ * matched text is typed into it.
+ */
+export default function nodeInputRule(config: {
+ find: InputRuleFinder,
+ type: NodeType,
+ getAttributes?:
+ | Record
+ | ((match: ExtendedRegExpMatchArray) => Record)
+ | false
+ | null
+ ,
+}) {
+ return new InputRule({
+ find: config.find,
+ handler: ({ state, range, match }) => {
+ const attributes = callOrReturn(config.getAttributes, undefined, match) || {}
+ const { tr } = state
+ const start = range.from
+ let end = range.to
- if (match[1]) {
- const offset = match[0].lastIndexOf(match[1])
- let matchStart = start + offset
- if (matchStart > end) {
- matchStart = end
- } else {
- end = matchStart + match[1].length
- }
+ if (match[1]) {
+ const offset = match[0].lastIndexOf(match[1])
+ let matchStart = start + offset
- // insert last typed character
- const lastChar = match[0][match[0].length - 1]
- tr.insertText(lastChar, start + match[0].length - 1)
+ if (matchStart > end) {
+ matchStart = end
+ } else {
+ end = matchStart + match[1].length
+ }
- // insert node from input rule
- tr.replaceWith(matchStart, end, type.create(attributes))
- } else if (match[0]) {
- tr.replaceWith(start, end, type.create(attributes))
- }
+ // insert last typed character
+ const lastChar = match[0][match[0].length - 1]
+ tr.insertText(lastChar, start + match[0].length - 1)
- return tr
+ // insert node from input rule
+ tr.replaceWith(matchStart, end, config.type.create(attributes))
+ } else if (match[0]) {
+ tr.replaceWith(start, end, config.type.create(attributes))
+ }
+ },
})
}
diff --git a/packages/core/src/inputRules/textInputRule.ts b/packages/core/src/inputRules/textInputRule.ts
new file mode 100644
index 0000000000..6e21bea09a
--- /dev/null
+++ b/packages/core/src/inputRules/textInputRule.ts
@@ -0,0 +1,35 @@
+import { InputRule, InputRuleFinder } from '../InputRule'
+
+/**
+ * Build an input rule that replaces text when the
+ * matched text is typed into it.
+ */
+export default function textInputRule(config: {
+ find: InputRuleFinder,
+ replace: string,
+}) {
+ return new InputRule({
+ find: config.find,
+ handler: ({ state, range, match }) => {
+ let insert = config.replace
+ let start = range.from
+ const end = range.to
+
+ if (match[1]) {
+ const offset = match[0].lastIndexOf(match[1])
+
+ insert += match[0].slice(offset + match[1].length)
+ start += offset
+
+ const cutOff = start - end
+
+ if (cutOff > 0) {
+ insert = match[0].slice(offset - cutOff, offset) + insert
+ start = end
+ }
+ }
+
+ state.tr.insertText(insert, start, end)
+ },
+ })
+}
diff --git a/packages/core/src/inputRules/textblockTypeInputRule.ts b/packages/core/src/inputRules/textblockTypeInputRule.ts
new file mode 100644
index 0000000000..9f08f1894c
--- /dev/null
+++ b/packages/core/src/inputRules/textblockTypeInputRule.ts
@@ -0,0 +1,37 @@
+import { InputRule, InputRuleFinder } from '../InputRule'
+import { NodeType } from 'prosemirror-model'
+import { ExtendedRegExpMatchArray } from '../types'
+import callOrReturn from '../utilities/callOrReturn'
+
+/**
+ * Build an input rule that changes the type of a textblock when the
+ * matched text is typed into it. When using a regular expresion you’ll
+ * probably want the regexp to start with `^`, so that the pattern can
+ * only occur at the start of a textblock.
+ */
+export default function textblockTypeInputRule(config: {
+ find: InputRuleFinder,
+ type: NodeType,
+ getAttributes?:
+ | Record
+ | ((match: ExtendedRegExpMatchArray) => Record)
+ | false
+ | null
+ ,
+}) {
+ return new InputRule({
+ find: config.find,
+ handler: ({ state, range, match }) => {
+ const $start = state.doc.resolve(range.from)
+ const attributes = callOrReturn(config.getAttributes, undefined, match) || {}
+
+ if (!$start.node(-1).canReplaceWith($start.index(-1), $start.indexAfter(-1), config.type)) {
+ return null
+ }
+
+ state.tr
+ .delete(range.from, range.to)
+ .setBlockType(range.from, range.from, config.type, attributes)
+ },
+ })
+}
diff --git a/packages/core/src/inputRules/wrappingInputRule.ts b/packages/core/src/inputRules/wrappingInputRule.ts
new file mode 100644
index 0000000000..79560f61c3
--- /dev/null
+++ b/packages/core/src/inputRules/wrappingInputRule.ts
@@ -0,0 +1,59 @@
+import { InputRule, InputRuleFinder } from '../InputRule'
+import { NodeType, Node as ProseMirrorNode } from 'prosemirror-model'
+import { findWrapping, canJoin } from 'prosemirror-transform'
+import { ExtendedRegExpMatchArray } from '../types'
+import callOrReturn from '../utilities/callOrReturn'
+
+/**
+ * Build an input rule for automatically wrapping a textblock when a
+ * given string is typed. When using a regular expresion you’ll
+ * probably want the regexp to start with `^`, so that the pattern can
+ * only occur at the start of a textblock.
+ *
+ * `type` is the type of node to wrap in.
+ *
+ * By default, if there’s a node with the same type above the newly
+ * wrapped node, the rule will try to join those
+ * two nodes. You can pass a join predicate, which takes a regular
+ * expression match and the node before the wrapped node, and can
+ * return a boolean to indicate whether a join should happen.
+ */
+export default function wrappingInputRule(config: {
+ find: InputRuleFinder,
+ type: NodeType,
+ getAttributes?:
+ | Record
+ | ((match: ExtendedRegExpMatchArray) => Record)
+ | false
+ | null
+ ,
+ joinPredicate?: (match: ExtendedRegExpMatchArray, node: ProseMirrorNode) => boolean,
+}) {
+ return new InputRule({
+ find: config.find,
+ handler: ({ state, range, match }) => {
+ const attributes = callOrReturn(config.getAttributes, undefined, match) || {}
+ const tr = state.tr.delete(range.from, range.to)
+ const $start = tr.doc.resolve(range.from)
+ const blockRange = $start.blockRange()
+ const wrapping = blockRange && findWrapping(blockRange, config.type, attributes)
+
+ if (!wrapping) {
+ return null
+ }
+
+ tr.wrap(blockRange, wrapping)
+
+ const before = tr.doc.resolve(range.from - 1).nodeBefore
+
+ if (
+ before
+ && before.type === config.type
+ && canJoin(tr.doc, range.from - 1)
+ && (!config.joinPredicate || config.joinPredicate(match, before))
+ ) {
+ tr.join(range.from - 1)
+ }
+ },
+ })
+}
diff --git a/packages/core/src/pasteRules/markPasteRule.ts b/packages/core/src/pasteRules/markPasteRule.ts
index 3e11ef1e4c..5deeedd230 100644
--- a/packages/core/src/pasteRules/markPasteRule.ts
+++ b/packages/core/src/pasteRules/markPasteRule.ts
@@ -1,76 +1,70 @@
-import { Plugin, PluginKey } from 'prosemirror-state'
-import { Slice, Fragment, MarkType } from 'prosemirror-model'
+import { PasteRule, PasteRuleFinder } from '../PasteRule'
+import { MarkType } from 'prosemirror-model'
+import getMarksBetween from '../helpers/getMarksBetween'
+import callOrReturn from '../utilities/callOrReturn'
+import { ExtendedRegExpMatchArray } from '../types'
-export default function (
- regexp: RegExp,
+/**
+ * Build an paste rule that adds a mark when the
+ * matched text is pasted into it.
+ */
+export default function markPasteRule(config: {
+ find: PasteRuleFinder,
type: MarkType,
getAttributes?:
| Record
- | ((match: RegExpExecArray) => Record)
+ | ((match: ExtendedRegExpMatchArray) => Record)
| false
| null
,
-): Plugin {
- const handler = (fragment: Fragment, parent?: any) => {
- const nodes: any[] = []
+}) {
+ return new PasteRule({
+ find: config.find,
+ handler: ({ state, range, match }) => {
+ const attributes = callOrReturn(config.getAttributes, undefined, match)
- fragment.forEach(child => {
- if (child.isText && child.text) {
- const { text } = child
- let pos = 0
- let match
+ if (attributes === false || attributes === null) {
+ return
+ }
- // eslint-disable-next-line
- while ((match = regexp.exec(text)) !== null) {
- const outerMatch = Math.max(match.length - 2, 0)
- const innerMatch = Math.max(match.length - 1, 0)
+ const { tr } = state
+ const captureGroup = match[match.length - 1]
+ const fullMatch = match[0]
+ let markEnd = range.to
- if (parent?.type.allowsMarkType(type)) {
- const start = match.index
- const matchStart = start + match[0].indexOf(match[outerMatch])
- const matchEnd = matchStart + match[outerMatch].length
- const textStart = matchStart + match[outerMatch].lastIndexOf(match[innerMatch])
- const textEnd = textStart + match[innerMatch].length
- const attrs = getAttributes instanceof Function
- ? getAttributes(match)
- : getAttributes
+ if (captureGroup) {
+ const startSpaces = fullMatch.search(/\S/)
+ const textStart = range.from + fullMatch.indexOf(captureGroup)
+ const textEnd = textStart + captureGroup.length
- if (!attrs && attrs !== undefined) {
- continue
- }
+ const excludedMarks = getMarksBetween(range.from, range.to, state)
+ .filter(item => {
+ // TODO: PR to add excluded to MarkType
+ // @ts-ignore
+ const { excluded } = item.mark.type
- // adding text before markdown to nodes
- if (matchStart > 0) {
- nodes.push(child.cut(pos, matchStart))
- }
+ return excluded.find((type: MarkType) => type.name === config.type.name)
+ })
+ .filter(item => item.to > textStart)
- // adding the markdown part to nodes
- nodes.push(child
- .cut(textStart, textEnd)
- .mark(type.create(attrs).addToSet(child.marks)))
+ if (excludedMarks.length) {
+ return null
+ }
- pos = matchEnd
- }
+ if (textEnd < range.to) {
+ tr.delete(textEnd, range.to)
}
- // adding rest of text to nodes
- if (pos < text.length) {
- nodes.push(child.cut(pos))
+ if (textStart > range.from) {
+ tr.delete(range.from + startSpaces, textStart)
}
- } else {
- nodes.push(child.copy(handler(child.content, child)))
- }
- })
- return Fragment.fromArray(nodes)
- }
+ markEnd = range.from + startSpaces + captureGroup.length
- return new Plugin({
- key: new PluginKey('markPasteRule'),
- props: {
- transformPasted: slice => {
- return new Slice(handler(slice.content), slice.openStart, slice.openEnd)
- },
+ tr.addMark(range.from + startSpaces, markEnd, config.type.create(attributes || {}))
+
+ tr.removeStoredMark(config.type)
+ }
},
})
}
diff --git a/packages/core/src/pasteRules/textPasteRule.ts b/packages/core/src/pasteRules/textPasteRule.ts
new file mode 100644
index 0000000000..199baf4848
--- /dev/null
+++ b/packages/core/src/pasteRules/textPasteRule.ts
@@ -0,0 +1,35 @@
+import { PasteRule, PasteRuleFinder } from '../PasteRule'
+
+/**
+ * Build an paste rule that replaces text when the
+ * matched text is pasted into it.
+ */
+export default function textPasteRule(config: {
+ find: PasteRuleFinder,
+ replace: string,
+}) {
+ return new PasteRule({
+ find: config.find,
+ handler: ({ state, range, match }) => {
+ let insert = config.replace
+ let start = range.from
+ const end = range.to
+
+ if (match[1]) {
+ const offset = match[0].lastIndexOf(match[1])
+
+ insert += match[0].slice(offset + match[1].length)
+ start += offset
+
+ const cutOff = start - end
+
+ if (cutOff > 0) {
+ insert = match[0].slice(offset - cutOff, offset) + insert
+ start = end
+ }
+ }
+
+ state.tr.insertText(insert, start, end)
+ },
+ })
+}
diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts
index a0d6f1290c..deeb3a9723 100644
--- a/packages/core/src/types.ts
+++ b/packages/core/src/types.ts
@@ -229,3 +229,7 @@ export type TextSerializer = (props: {
parent: ProseMirrorNode,
index: number,
}) => string
+
+export type ExtendedRegExpMatchArray = RegExpMatchArray & {
+ data?: Record,
+}
diff --git a/packages/core/src/utilities/callOrReturn.ts b/packages/core/src/utilities/callOrReturn.ts
index a87ee60fbf..b479492744 100644
--- a/packages/core/src/utilities/callOrReturn.ts
+++ b/packages/core/src/utilities/callOrReturn.ts
@@ -1,4 +1,5 @@
import { MaybeReturnType } from '../types'
+import isFunction from './isFunction'
/**
* Optionally calls `value` as a function.
@@ -8,7 +9,7 @@ import { MaybeReturnType } from '../types'
* @param props Optional props to pass to function.
*/
export default function callOrReturn(value: T, context: any = undefined, ...props: any[]): MaybeReturnType {
- if (typeof value === 'function') {
+ if (isFunction(value)) {
if (context) {
return value.bind(context)(...props)
}
diff --git a/packages/core/src/utilities/isClass.ts b/packages/core/src/utilities/isClass.ts
index a35ba2e74e..d3d1f74786 100644
--- a/packages/core/src/utilities/isClass.ts
+++ b/packages/core/src/utilities/isClass.ts
@@ -1,5 +1,5 @@
-export default function isClass(item: any): boolean {
- if (item.constructor?.toString().substring(0, 5) !== 'class') {
+export default function isClass(value: any): boolean {
+ if (value.constructor?.toString().substring(0, 5) !== 'class') {
return false
}
diff --git a/packages/core/src/utilities/isEmptyObject.ts b/packages/core/src/utilities/isEmptyObject.ts
index 4af643191a..da956393ff 100644
--- a/packages/core/src/utilities/isEmptyObject.ts
+++ b/packages/core/src/utilities/isEmptyObject.ts
@@ -1,3 +1,3 @@
-export default function isEmptyObject(object = {}): boolean {
- return Object.keys(object).length === 0 && object.constructor === Object
+export default function isEmptyObject(value = {}): boolean {
+ return Object.keys(value).length === 0 && value.constructor === Object
}
diff --git a/packages/core/src/utilities/isFunction.ts b/packages/core/src/utilities/isFunction.ts
new file mode 100644
index 0000000000..1a08f66545
--- /dev/null
+++ b/packages/core/src/utilities/isFunction.ts
@@ -0,0 +1,3 @@
+export default function isObject(value: any): value is Function {
+ return typeof value === 'function'
+}
diff --git a/packages/core/src/utilities/isObject.ts b/packages/core/src/utilities/isObject.ts
index 2de052f309..f89f1c3654 100644
--- a/packages/core/src/utilities/isObject.ts
+++ b/packages/core/src/utilities/isObject.ts
@@ -1,10 +1,10 @@
import isClass from './isClass'
-export default function isObject(item: any): boolean {
+export default function isObject(value: any): boolean {
return (
- item
- && typeof item === 'object'
- && !Array.isArray(item)
- && !isClass(item)
+ value
+ && typeof value === 'object'
+ && !Array.isArray(value)
+ && !isClass(value)
)
}
diff --git a/packages/core/src/utilities/isPlainObject.ts b/packages/core/src/utilities/isPlainObject.ts
index 9c440725c1..febff25b54 100644
--- a/packages/core/src/utilities/isPlainObject.ts
+++ b/packages/core/src/utilities/isPlainObject.ts
@@ -1,10 +1,10 @@
// see: https://github.com/mesqueeb/is-what/blob/88d6e4ca92fb2baab6003c54e02eedf4e729e5ab/src/index.ts
-function getType(payload: any): string {
- return Object.prototype.toString.call(payload).slice(8, -1)
+function getType(value: any): string {
+ return Object.prototype.toString.call(value).slice(8, -1)
}
-export default function isPlainObject(payload: any): payload is Record {
- if (getType(payload) !== 'Object') return false
- return payload.constructor === Object && Object.getPrototypeOf(payload) === Object.prototype
+export default function isPlainObject(value: any): value is Record {
+ if (getType(value) !== 'Object') return false
+ return value.constructor === Object && Object.getPrototypeOf(value) === Object.prototype
}
diff --git a/packages/core/src/utilities/isRegExp.ts b/packages/core/src/utilities/isRegExp.ts
index a18be7d9f3..57feabef86 100644
--- a/packages/core/src/utilities/isRegExp.ts
+++ b/packages/core/src/utilities/isRegExp.ts
@@ -1,3 +1,3 @@
-export default function isRegExp(value: any): boolean {
+export default function isRegExp(value: any): value is RegExp {
return Object.prototype.toString.call(value) === '[object RegExp]'
}
diff --git a/packages/core/src/utilities/isString.ts b/packages/core/src/utilities/isString.ts
new file mode 100644
index 0000000000..b3ba58871a
--- /dev/null
+++ b/packages/core/src/utilities/isString.ts
@@ -0,0 +1,3 @@
+export default function isString(value: any): value is string {
+ return typeof value === 'string'
+}
diff --git a/packages/extension-blockquote/package.json b/packages/extension-blockquote/package.json
index 63600920db..a5bf4e1e93 100644
--- a/packages/extension-blockquote/package.json
+++ b/packages/extension-blockquote/package.json
@@ -23,9 +23,6 @@
"peerDependencies": {
"@tiptap/core": "^2.0.0-beta.1"
},
- "dependencies": {
- "prosemirror-inputrules": "^1.1.3"
- },
"repository": {
"type": "git",
"url": "https://github.com/ueberdosis/tiptap",
diff --git a/packages/extension-blockquote/src/blockquote.ts b/packages/extension-blockquote/src/blockquote.ts
index 16b8988904..e4c339b3fc 100644
--- a/packages/extension-blockquote/src/blockquote.ts
+++ b/packages/extension-blockquote/src/blockquote.ts
@@ -1,5 +1,4 @@
-import { Node, mergeAttributes } from '@tiptap/core'
-import { wrappingInputRule } from 'prosemirror-inputrules'
+import { Node, mergeAttributes, wrappingInputRule } from '@tiptap/core'
export interface BlockquoteOptions {
HTMLAttributes: Record,
@@ -72,7 +71,10 @@ export const Blockquote = Node.create({
addInputRules() {
return [
- wrappingInputRule(inputRegex, this.type),
+ wrappingInputRule({
+ find: inputRegex,
+ type: this.type,
+ }),
]
},
})
diff --git a/packages/extension-bold/src/bold.ts b/packages/extension-bold/src/bold.ts
index a39e244397..50c0098e73 100644
--- a/packages/extension-bold/src/bold.ts
+++ b/packages/extension-bold/src/bold.ts
@@ -82,15 +82,27 @@ export const Bold = Mark.create({
addInputRules() {
return [
- markInputRule(starInputRegex, this.type),
- markInputRule(underscoreInputRegex, this.type),
+ markInputRule({
+ find: starInputRegex,
+ type: this.type,
+ }),
+ markInputRule({
+ find: underscoreInputRegex,
+ type: this.type,
+ }),
]
},
addPasteRules() {
return [
- markPasteRule(starPasteRegex, this.type),
- markPasteRule(underscorePasteRegex, this.type),
+ markPasteRule({
+ find: starPasteRegex,
+ type: this.type,
+ }),
+ markPasteRule({
+ find: underscorePasteRegex,
+ type: this.type,
+ }),
]
},
})
diff --git a/packages/extension-bullet-list/package.json b/packages/extension-bullet-list/package.json
index a7bd2031ca..6b9e45e5f4 100644
--- a/packages/extension-bullet-list/package.json
+++ b/packages/extension-bullet-list/package.json
@@ -23,9 +23,6 @@
"peerDependencies": {
"@tiptap/core": "^2.0.0-beta.1"
},
- "dependencies": {
- "prosemirror-inputrules": "^1.1.3"
- },
"repository": {
"type": "git",
"url": "https://github.com/ueberdosis/tiptap",
diff --git a/packages/extension-bullet-list/src/bullet-list.ts b/packages/extension-bullet-list/src/bullet-list.ts
index 8c3c432f04..eb2144351a 100644
--- a/packages/extension-bullet-list/src/bullet-list.ts
+++ b/packages/extension-bullet-list/src/bullet-list.ts
@@ -1,5 +1,4 @@
-import { Node, mergeAttributes } from '@tiptap/core'
-import { wrappingInputRule } from 'prosemirror-inputrules'
+import { Node, mergeAttributes, wrappingInputRule } from '@tiptap/core'
export interface BulletListOptions {
HTMLAttributes: Record,
@@ -55,7 +54,10 @@ export const BulletList = Node.create({
addInputRules() {
return [
- wrappingInputRule(inputRegex, this.type),
+ wrappingInputRule({
+ find: inputRegex,
+ type: this.type,
+ }),
]
},
})
diff --git a/packages/extension-code-block/package.json b/packages/extension-code-block/package.json
index 24213b74a5..d82e9fdee2 100644
--- a/packages/extension-code-block/package.json
+++ b/packages/extension-code-block/package.json
@@ -23,9 +23,6 @@
"peerDependencies": {
"@tiptap/core": "^2.0.0-beta.1"
},
- "dependencies": {
- "prosemirror-inputrules": "^1.1.3"
- },
"repository": {
"type": "git",
"url": "https://github.com/ueberdosis/tiptap",
diff --git a/packages/extension-code-block/src/code-block.ts b/packages/extension-code-block/src/code-block.ts
index e590043e45..125cba0a49 100644
--- a/packages/extension-code-block/src/code-block.ts
+++ b/packages/extension-code-block/src/code-block.ts
@@ -1,5 +1,4 @@
-import { Node } from '@tiptap/core'
-import { textblockTypeInputRule } from 'prosemirror-inputrules'
+import { Node, textblockTypeInputRule } from '@tiptap/core'
export interface CodeBlockOptions {
languageClassPrefix: string,
@@ -21,8 +20,8 @@ declare module '@tiptap/core' {
}
}
-export const backtickInputRegex = /^```(?[a-z]*)? $/
-export const tildeInputRegex = /^~~~(?[a-z]*)? $/
+export const backtickInputRegex = /^```(?[a-z]*)?[\s\n]$/
+export const tildeInputRegex = /^~~~(?[a-z]*)?[\s\n]$/
export const CodeBlock = Node.create({
name: 'codeBlock',
@@ -121,8 +120,16 @@ export const CodeBlock = Node.create({
addInputRules() {
return [
- textblockTypeInputRule(backtickInputRegex, this.type, ({ groups }: any) => groups),
- textblockTypeInputRule(tildeInputRegex, this.type, ({ groups }: any) => groups),
+ textblockTypeInputRule({
+ find: backtickInputRegex,
+ type: this.type,
+ getAttributes: ({ groups }) => groups,
+ }),
+ textblockTypeInputRule({
+ find: tildeInputRegex,
+ type: this.type,
+ getAttributes: ({ groups }) => groups,
+ }),
]
},
})
diff --git a/packages/extension-code/src/code.ts b/packages/extension-code/src/code.ts
index 8710661221..12b84b7590 100644
--- a/packages/extension-code/src/code.ts
+++ b/packages/extension-code/src/code.ts
@@ -40,6 +40,8 @@ export const Code = Mark.create({
excludes: '_',
+ code: true,
+
parseHTML() {
return [
{ tag: 'code' },
@@ -72,13 +74,19 @@ export const Code = Mark.create({
addInputRules() {
return [
- markInputRule(inputRegex, this.type),
+ markInputRule({
+ find: inputRegex,
+ type: this.type,
+ }),
]
},
addPasteRules() {
return [
- markPasteRule(pasteRegex, this.type),
+ markPasteRule({
+ find: pasteRegex,
+ type: this.type,
+ }),
]
},
})
diff --git a/packages/extension-heading/package.json b/packages/extension-heading/package.json
index bdb5e2e3bc..8888bdd066 100644
--- a/packages/extension-heading/package.json
+++ b/packages/extension-heading/package.json
@@ -23,9 +23,6 @@
"peerDependencies": {
"@tiptap/core": "^2.0.0-beta.1"
},
- "dependencies": {
- "prosemirror-inputrules": "^1.1.3"
- },
"repository": {
"type": "git",
"url": "https://github.com/ueberdosis/tiptap",
diff --git a/packages/extension-heading/src/heading.ts b/packages/extension-heading/src/heading.ts
index a0f493ec0d..9d84e1a4eb 100644
--- a/packages/extension-heading/src/heading.ts
+++ b/packages/extension-heading/src/heading.ts
@@ -1,5 +1,4 @@
-import { Node, mergeAttributes } from '@tiptap/core'
-import { textblockTypeInputRule } from 'prosemirror-inputrules'
+import { Node, mergeAttributes, textblockTypeInputRule } from '@tiptap/core'
type Level = 1 | 2 | 3 | 4 | 5 | 6
@@ -93,7 +92,13 @@ export const Heading = Node.create({
addInputRules() {
return this.options.levels.map(level => {
- return textblockTypeInputRule(new RegExp(`^(#{1,${level}})\\s$`), this.type, { level })
+ return textblockTypeInputRule({
+ find: new RegExp(`^(#{1,${level}})\\s$`),
+ type: this.type,
+ getAttributes: {
+ level,
+ },
+ })
})
},
})
diff --git a/packages/extension-highlight/src/highlight.ts b/packages/extension-highlight/src/highlight.ts
index 84d90cae02..b37e5fc0a0 100644
--- a/packages/extension-highlight/src/highlight.ts
+++ b/packages/extension-highlight/src/highlight.ts
@@ -97,13 +97,19 @@ export const Highlight = Mark.create({
addInputRules() {
return [
- markInputRule(inputRegex, this.type),
+ markInputRule({
+ find: inputRegex,
+ type: this.type,
+ }),
]
},
addPasteRules() {
return [
- markPasteRule(pasteRegex, this.type),
+ markPasteRule({
+ find: pasteRegex,
+ type: this.type,
+ }),
]
},
})
diff --git a/packages/extension-horizontal-rule/src/horizontal-rule.ts b/packages/extension-horizontal-rule/src/horizontal-rule.ts
index 03ebd40fc1..57d938f239 100644
--- a/packages/extension-horizontal-rule/src/horizontal-rule.ts
+++ b/packages/extension-horizontal-rule/src/horizontal-rule.ts
@@ -92,7 +92,10 @@ export const HorizontalRule = Node.create({
addInputRules() {
return [
- nodeInputRule(/^(?:---|—-|___\s|\*\*\*\s)$/, this.type),
+ nodeInputRule({
+ find: /^(?:---|—-|___\s|\*\*\*\s)$/,
+ type: this.type,
+ }),
]
},
})
diff --git a/packages/extension-image/src/image.ts b/packages/extension-image/src/image.ts
index 7bfe871b45..66e01791c0 100644
--- a/packages/extension-image/src/image.ts
+++ b/packages/extension-image/src/image.ts
@@ -79,10 +79,14 @@ export const Image = Node.create({
addInputRules() {
return [
- nodeInputRule(inputRegex, this.type, match => {
- const [, alt, src, title] = match
+ nodeInputRule({
+ find: inputRegex,
+ type: this.type,
+ getAttributes: match => {
+ const [, alt, src, title] = match
- return { src, alt, title }
+ return { src, alt, title }
+ },
}),
]
},
diff --git a/packages/extension-italic/src/italic.ts b/packages/extension-italic/src/italic.ts
index 9cc818cfb4..0bb1260fb8 100644
--- a/packages/extension-italic/src/italic.ts
+++ b/packages/extension-italic/src/italic.ts
@@ -81,15 +81,27 @@ export const Italic = Mark.create({
addInputRules() {
return [
- markInputRule(starInputRegex, this.type),
- markInputRule(underscoreInputRegex, this.type),
+ markInputRule({
+ find: starInputRegex,
+ type: this.type,
+ }),
+ markInputRule({
+ find: underscoreInputRegex,
+ type: this.type,
+ }),
]
},
addPasteRules() {
return [
- markPasteRule(starPasteRegex, this.type),
- markPasteRule(underscorePasteRegex, this.type),
+ markPasteRule({
+ find: starPasteRegex,
+ type: this.type,
+ }),
+ markPasteRule({
+ find: underscorePasteRegex,
+ type: this.type,
+ }),
]
},
})
diff --git a/packages/extension-link/package.json b/packages/extension-link/package.json
index 7c1f4dc314..7c2af38ea0 100644
--- a/packages/extension-link/package.json
+++ b/packages/extension-link/package.json
@@ -24,6 +24,7 @@
"@tiptap/core": "^2.0.0-beta.1"
},
"dependencies": {
+ "linkifyjs": "^3.0.1",
"prosemirror-state": "^1.3.4"
},
"repository": {
diff --git a/packages/extension-link/src/link.ts b/packages/extension-link/src/link.ts
index f906bd0290..25dc49801c 100644
--- a/packages/extension-link/src/link.ts
+++ b/packages/extension-link/src/link.ts
@@ -4,6 +4,7 @@ import {
mergeAttributes,
} from '@tiptap/core'
import { Plugin, PluginKey } from 'prosemirror-state'
+import { find } from 'linkifyjs'
export interface LinkOptions {
/**
@@ -39,16 +40,6 @@ declare module '@tiptap/core' {
}
}
-/**
- * A regex that matches any string that contains a link
- */
-export const pasteRegex = /https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z]{2,}\b(?:[-a-zA-Z0-9@:%._+~#=?!&/]*)(?:[-a-zA-Z0-9@:%._+~#=?!&/]*)/gi
-
-/**
- * A regex that matches an url
- */
-export const pasteRegexExact = /^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z]{2,}\b(?:[-a-zA-Z0-9@:%._+~#=?!&/]*)(?:[-a-zA-Z0-9@:%._+~#=?!&/]*)$/gi
-
export const Link = Mark.create({
name: 'link',
@@ -102,7 +93,19 @@ export const Link = Mark.create({
addPasteRules() {
return [
- markPasteRule(pasteRegex, this.type, match => ({ href: match[0] })),
+ markPasteRule({
+ find: text => find(text)
+ .filter(link => link.isLink)
+ .map(link => ({
+ text: link.value,
+ index: link.start,
+ data: link,
+ })),
+ type: this.type,
+ getAttributes: match => ({
+ href: match.data?.href,
+ }),
+ }),
]
},
@@ -151,12 +154,15 @@ export const Link = Mark.create({
textContent += node.textContent
})
- if (!textContent || !textContent.match(pasteRegexExact)) {
+ const link = find(textContent)
+ .find(item => item.isLink && item.value === textContent)
+
+ if (!textContent || !link) {
return false
}
this.editor.commands.setMark(this.type, {
- href: textContent,
+ href: link.href,
})
return true
diff --git a/packages/extension-ordered-list/package.json b/packages/extension-ordered-list/package.json
index 61eb8684d5..3bf260df24 100644
--- a/packages/extension-ordered-list/package.json
+++ b/packages/extension-ordered-list/package.json
@@ -23,9 +23,6 @@
"peerDependencies": {
"@tiptap/core": "^2.0.0-beta.1"
},
- "dependencies": {
- "prosemirror-inputrules": "^1.1.3"
- },
"repository": {
"type": "git",
"url": "https://github.com/ueberdosis/tiptap",
diff --git a/packages/extension-ordered-list/src/ordered-list.ts b/packages/extension-ordered-list/src/ordered-list.ts
index d3205eb37b..6ba22f5e43 100644
--- a/packages/extension-ordered-list/src/ordered-list.ts
+++ b/packages/extension-ordered-list/src/ordered-list.ts
@@ -1,5 +1,4 @@
-import { Node, mergeAttributes } from '@tiptap/core'
-import { wrappingInputRule } from 'prosemirror-inputrules'
+import { Node, mergeAttributes, wrappingInputRule } from '@tiptap/core'
export interface OrderedListOptions {
HTMLAttributes: Record,
@@ -74,12 +73,12 @@ export const OrderedList = Node.create({
addInputRules() {
return [
- wrappingInputRule(
- inputRegex,
- this.type,
- match => ({ start: +match[1] }),
- (match, node) => node.childCount + node.attrs.start === +match[1],
- ),
+ wrappingInputRule({
+ find: inputRegex,
+ type: this.type,
+ getAttributes: match => ({ start: +match[1] }),
+ joinPredicate: (match, node) => node.childCount + node.attrs.start === +match[1],
+ }),
]
},
})
diff --git a/packages/extension-strike/src/strike.ts b/packages/extension-strike/src/strike.ts
index 879ad047d0..cd09ef865f 100644
--- a/packages/extension-strike/src/strike.ts
+++ b/packages/extension-strike/src/strike.ts
@@ -83,13 +83,19 @@ export const Strike = Mark.create({
addInputRules() {
return [
- markInputRule(inputRegex, this.type),
+ markInputRule({
+ find: inputRegex,
+ type: this.type,
+ }),
]
},
addPasteRules() {
return [
- markPasteRule(pasteRegex, this.type),
+ markPasteRule({
+ find: pasteRegex,
+ type: this.type,
+ }),
]
},
})
diff --git a/packages/extension-task-item/package.json b/packages/extension-task-item/package.json
index b1091cfc7a..dddeb2f04a 100644
--- a/packages/extension-task-item/package.json
+++ b/packages/extension-task-item/package.json
@@ -23,9 +23,6 @@
"peerDependencies": {
"@tiptap/core": "^2.0.0-beta.1"
},
- "dependencies": {
- "prosemirror-inputrules": "^1.1.3"
- },
"repository": {
"type": "git",
"url": "https://github.com/ueberdosis/tiptap",
diff --git a/packages/extension-task-item/src/task-item.ts b/packages/extension-task-item/src/task-item.ts
index c20668c4ca..9221465cb5 100644
--- a/packages/extension-task-item/src/task-item.ts
+++ b/packages/extension-task-item/src/task-item.ts
@@ -1,5 +1,4 @@
-import { Node, mergeAttributes } from '@tiptap/core'
-import { wrappingInputRule } from 'prosemirror-inputrules'
+import { Node, mergeAttributes, wrappingInputRule } from '@tiptap/core'
export interface TaskItemOptions {
nested: boolean,
@@ -146,13 +145,13 @@ export const TaskItem = Node.create({
addInputRules() {
return [
- wrappingInputRule(
- inputRegex,
- this.type,
- match => ({
+ wrappingInputRule({
+ find: inputRegex,
+ type: this.type,
+ getAttributes: match => ({
checked: match[match.length - 1] === 'x',
}),
- ),
+ }),
]
},
})
diff --git a/packages/extension-typography/package.json b/packages/extension-typography/package.json
index cc91e89d57..b00689f353 100644
--- a/packages/extension-typography/package.json
+++ b/packages/extension-typography/package.json
@@ -23,9 +23,6 @@
"peerDependencies": {
"@tiptap/core": "^2.0.0-beta.1"
},
- "dependencies": {
- "prosemirror-inputrules": "^1.1.3"
- },
"repository": {
"type": "git",
"url": "https://github.com/ueberdosis/tiptap",
diff --git a/packages/extension-typography/src/typography.ts b/packages/extension-typography/src/typography.ts
index 60867618d3..bae235cfa7 100644
--- a/packages/extension-typography/src/typography.ts
+++ b/packages/extension-typography/src/typography.ts
@@ -1,29 +1,109 @@
-import { Extension } from '@tiptap/core'
-import {
- emDash,
- ellipsis,
- openDoubleQuote,
- closeDoubleQuote,
- openSingleQuote,
- closeSingleQuote,
- InputRule,
-} from 'prosemirror-inputrules'
-
-export const leftArrow = new InputRule(/<-$/, '←')
-export const rightArrow = new InputRule(/->$/, '→')
-export const copyright = new InputRule(/\(c\)$/, '©')
-export const trademark = new InputRule(/\(tm\)$/, '™')
-export const registeredTrademark = new InputRule(/\(r\)$/, '®')
-export const oneHalf = new InputRule(/1\/2$/, '½')
-export const plusMinus = new InputRule(/\+\/-$/, '±')
-export const notEqual = new InputRule(/!=$/, '≠')
-export const laquo = new InputRule(/<<$/, '«')
-export const raquo = new InputRule(/>>$/, '»')
-export const multiplication = new InputRule(/\d+\s?([*x])\s?\d+$/, '×')
-export const superscriptTwo = new InputRule(/\^2$/, '²')
-export const superscriptThree = new InputRule(/\^3$/, '³')
-export const oneQuarter = new InputRule(/1\/4$/, '¼')
-export const threeQuarters = new InputRule(/3\/4$/, '¾')
+import { Extension, textInputRule } from '@tiptap/core'
+
+export const emDash = textInputRule({
+ find: /--$/,
+ replace: '—',
+})
+
+export const ellipsis = textInputRule({
+ find: /\.\.\.$/,
+ replace: '…',
+})
+
+export const openDoubleQuote = textInputRule({
+ find: /(?:^|[\s{[(<'"\u2018\u201C])(")$/,
+ replace: '“',
+})
+
+export const closeDoubleQuote = textInputRule({
+ find: /"$/,
+ replace: '”',
+})
+
+export const openSingleQuote = textInputRule({
+ find: /(?:^|[\s{[(<'"\u2018\u201C])(')$/,
+ replace: '‘',
+})
+
+export const closeSingleQuote = textInputRule({
+ find: /'$/,
+ replace: '’',
+})
+
+export const leftArrow = textInputRule({
+ find: /<-$/,
+ replace: '←',
+})
+
+export const rightArrow = textInputRule({
+ find: /->$/,
+ replace: '→',
+})
+
+export const copyright = textInputRule({
+ find: /\(c\)$/,
+ replace: '©',
+})
+
+export const trademark = textInputRule({
+ find: /\(tm\)$/,
+ replace: '™',
+})
+
+export const registeredTrademark = textInputRule({
+ find: /\(r\)$/,
+ replace: '®',
+})
+
+export const oneHalf = textInputRule({
+ find: /1\/2$/,
+ replace: '½',
+})
+
+export const plusMinus = textInputRule({
+ find: /\+\/-$/,
+ replace: '±',
+})
+
+export const notEqual = textInputRule({
+ find: /!=$/,
+ replace: '≠',
+})
+
+export const laquo = textInputRule({
+ find: /<<$/,
+ replace: '«',
+})
+
+export const raquo = textInputRule({
+ find: />>$/,
+ replace: '»',
+})
+
+export const multiplication = textInputRule({
+ find: /\d+\s?([*x])\s?\d+$/,
+ replace: '×',
+})
+
+export const superscriptTwo = textInputRule({
+ find: /\^2$/,
+ replace: '²',
+})
+
+export const superscriptThree = textInputRule({
+ find: /\^3$/,
+ replace: '³',
+})
+
+export const oneQuarter = textInputRule({
+ find: /1\/4$/,
+ replace: '¼',
+})
+
+export const threeQuarters = textInputRule({
+ find: /3\/4$/,
+ replace: '¾',
+})
export const Typography = Extension.create({
name: 'typography',
diff --git a/tests/cypress/integration/extensions/link.spec.ts b/tests/cypress/integration/extensions/link.spec.ts
deleted file mode 100644
index a72c527f1b..0000000000
--- a/tests/cypress/integration/extensions/link.spec.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-///
-
-import { pasteRegex } from '@tiptap/extension-link'
-
-describe('link paste rules', () => {
- const validUrls = [
- 'https://example.com',
- 'https://example.com/with-path',
- 'http://example.com/with-http',
- 'https://www.example.com/with-www',
- 'https://www.example.com/with-numbers-123',
- 'https://www.example.com/with-parameters?var=true',
- 'https://www.example.com/with-multiple-parameters?var=true&foo=bar',
- 'https://www.example.com/with-spaces?var=true&foo=bar+3',
- 'https://www.example.com/with,comma',
- 'https://www.example.com/with(brackets)',
- 'https://www.example.com/with!exclamation!marks',
- 'http://thelongestdomainnameintheworldandthensomeandthensomemoreandmore.com/',
- 'https://example.longtopleveldomain',
- 'https://example-with-dashes.com',
- ]
-
- validUrls.forEach(url => {
- it(`paste regex matches url: ${url}`, {
- // every second test fails, but the second try succeeds
- retries: {
- runMode: 2,
- openMode: 2,
- },
- }, () => {
- // TODO: Check the regex capture group to see *what* is matched
- expect(url).to.match(pasteRegex)
- })
- })
-
- const invalidUrls = [
- 'ftp://www.example.com',
- ]
-
- invalidUrls.forEach(url => {
- it(`paste regex doesn’t match url: ${url}`, {
- // every second test fails, but the second try succeeds
- retries: {
- runMode: 2,
- openMode: 2,
- },
- }, () => {
- expect(url).to.not.match(pasteRegex)
- })
- })
-})
diff --git a/yarn.lock b/yarn.lock
index db76700fcc..51d43385b8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2100,14 +2100,6 @@
"@types/prosemirror-model" "*"
"@types/prosemirror-state" "*"
-"@types/prosemirror-inputrules@^1.0.4":
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/@types/prosemirror-inputrules/-/prosemirror-inputrules-1.0.4.tgz#4cb75054d954aa0f6f42099be05eb6c0e6958bae"
- integrity sha512-lJIMpOjO47SYozQybUkpV6QmfuQt7GZKHtVrvS+mR5UekA8NMC5HRIVMyaIauJLWhKU6oaNjpVaXdw41kh165g==
- dependencies:
- "@types/prosemirror-model" "*"
- "@types/prosemirror-state" "*"
-
"@types/prosemirror-keymap@^1.0.4":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@types/prosemirror-keymap/-/prosemirror-keymap-1.0.4.tgz#f73c79810e8d0e0a20d153d84f998f02e5afbc0c"
@@ -5922,6 +5914,11 @@ lines-and-columns@^1.1.6:
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
+linkifyjs@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-3.0.1.tgz#fda7b8d399eceef6fd7427f8a6e5d4f962ae74ed"
+ integrity sha512-HwXVwdNH1wESBfo2sH7Bkl+ywzbGA3+uJEfhquCyi/bMCa49bFUvd/re1NT1Lox/5jdnpQXzI9O/jykit71idg==
+
listr2@^3.8.3:
version "3.12.2"
resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.12.2.tgz#2d55cc627111603ad4768a9e87c9c7bb9b49997e"
@@ -7163,14 +7160,6 @@ prosemirror-history@^1.2.0:
prosemirror-transform "^1.0.0"
rope-sequence "^1.3.0"
-prosemirror-inputrules@^1.1.3:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/prosemirror-inputrules/-/prosemirror-inputrules-1.1.3.tgz#93f9199ca02473259c30d7e352e4c14022d54638"
- integrity sha512-ZaHCLyBtvbyIHv0f5p6boQTIJjlD6o2NPZiEaZWT2DA+j591zS29QQEMT4lBqwcLW3qRSf7ZvoKNbf05YrsStw==
- dependencies:
- prosemirror-state "^1.0.0"
- prosemirror-transform "^1.0.0"
-
prosemirror-keymap@^1.0.0, prosemirror-keymap@^1.1.2, prosemirror-keymap@^1.1.3:
version "1.1.4"
resolved "https://registry.yarnpkg.com/prosemirror-keymap/-/prosemirror-keymap-1.1.4.tgz#8b481bf8389a5ac40d38dbd67ec3da2c7eac6a6d"