fix: add way to cancel inputrules and pasterules (#2368)

Co-authored-by: Philipp Kühn <philippkuehn@MacBook-Pro-von-Philipp.local>
This commit is contained in:
Philipp Kühn
2022-01-10 14:43:56 +01:00
committed by GitHub
parent 5144a6cd1b
commit 669efd00e9
10 changed files with 112 additions and 86 deletions

View File

@@ -306,7 +306,7 @@ export class ExtensionManager {
editor, editor,
rules: inputRules, rules: inputRules,
}), }),
pasteRulesPlugin({ ...pasteRulesPlugin({
editor, editor,
rules: pasteRules, rules: pasteRules,
}), }),

View File

@@ -1,4 +1,9 @@
import { EditorState, Plugin, TextSelection } from 'prosemirror-state' import {
EditorState,
Plugin,
TextSelection,
Transaction,
} from 'prosemirror-state'
import { Editor } from './Editor' import { Editor } from './Editor'
import { CommandManager } from './CommandManager' import { CommandManager } from './CommandManager'
import { createChainableState } from './helpers/createChainableState' import { createChainableState } from './helpers/createChainableState'
@@ -33,7 +38,7 @@ export class InputRule {
commands: SingleCommands, commands: SingleCommands,
chain: () => ChainedCommands, chain: () => ChainedCommands,
can: () => CanCommands, can: () => CanCommands,
}) => void }) => Transaction | null
constructor(config: { constructor(config: {
find: InputRuleFinder, find: InputRuleFinder,
@@ -44,7 +49,7 @@ export class InputRule {
commands: SingleCommands, commands: SingleCommands,
chain: () => ChainedCommands, chain: () => ChainedCommands,
can: () => CanCommands, can: () => CanCommands,
}) => void, }) => Transaction | null,
}) { }) {
this.find = config.find this.find = config.find
this.handler = config.handler this.handler = config.handler
@@ -87,7 +92,7 @@ function run(config: {
text: string, text: string,
rules: InputRule[], rules: InputRule[],
plugin: Plugin, plugin: Plugin,
}): any { }): boolean {
const { const {
editor, editor,
from, from,
@@ -148,7 +153,7 @@ function run(config: {
state, state,
}) })
rule.handler({ const handler = rule.handler({
state, state,
range, range,
match, match,
@@ -158,7 +163,7 @@ function run(config: {
}) })
// stop if there are no changes // stop if there are no changes
if (!tr.steps.length) { if (!handler || !tr.steps.length) {
return return
} }

View File

@@ -1,4 +1,4 @@
import { EditorState, Plugin } from 'prosemirror-state' import { EditorState, Plugin, Transaction } from 'prosemirror-state'
import { Editor } from './Editor' import { Editor } from './Editor'
import { CommandManager } from './CommandManager' import { CommandManager } from './CommandManager'
import { createChainableState } from './helpers/createChainableState' import { createChainableState } from './helpers/createChainableState'
@@ -34,7 +34,7 @@ export class PasteRule {
commands: SingleCommands, commands: SingleCommands,
chain: () => ChainedCommands, chain: () => ChainedCommands,
can: () => CanCommands, can: () => CanCommands,
}) => void }) => Transaction | null
constructor(config: { constructor(config: {
find: PasteRuleFinder, find: PasteRuleFinder,
@@ -45,7 +45,7 @@ export class PasteRule {
commands: SingleCommands, commands: SingleCommands,
chain: () => ChainedCommands, chain: () => ChainedCommands,
can: () => CanCommands, can: () => CanCommands,
}) => void, }) => Transaction | null,
}) { }) {
this.find = config.find this.find = config.find
this.handler = config.handler this.handler = config.handler
@@ -88,15 +88,14 @@ function run(config: {
state: EditorState, state: EditorState,
from: number, from: number,
to: number, to: number,
rules: PasteRule[], rule: PasteRule,
plugin: Plugin, }): boolean {
}): any {
const { const {
editor, editor,
state, state,
from, from,
to, to,
rules, rule,
} = config } = config
const { commands, chain, can } = new CommandManager({ const { commands, chain, can } = new CommandManager({
@@ -104,6 +103,8 @@ function run(config: {
state, state,
}) })
const handlers: (Transaction | null)[] = []
state.doc.nodesBetween(from, to, (node, pos) => { state.doc.nodesBetween(from, to, (node, pos) => {
if (!node.isTextblock || node.type.spec.code) { if (!node.isTextblock || node.type.spec.code) {
return return
@@ -118,32 +119,36 @@ function run(config: {
'\ufffc', '\ufffc',
) )
rules.forEach(rule => { const matches = pasteRuleMatcherHandler(textToMatch, rule.find)
const matches = pasteRuleMatcherHandler(textToMatch, rule.find)
matches.forEach(match => { matches.forEach(match => {
if (match.index === undefined) { if (match.index === undefined) {
return return
} }
const start = resolvedFrom + match.index + 1 const start = resolvedFrom + match.index + 1
const end = start + match[0].length const end = start + match[0].length
const range = { const range = {
from: state.tr.mapping.map(start), from: state.tr.mapping.map(start),
to: state.tr.mapping.map(end), to: state.tr.mapping.map(end),
} }
rule.handler({ const handler = rule.handler({
state, state,
range, range,
match, match,
commands, commands,
chain, chain,
can, can,
})
}) })
handlers.push(handler)
}) })
}) })
const success = handlers.every(handler => handler !== null)
return success
} }
/** /**
@@ -151,65 +156,62 @@ function run(config: {
* text that matches any of the given rules to trigger the rules * text that matches any of the given rules to trigger the rules
* action. * action.
*/ */
export function pasteRulesPlugin(props: { editor: Editor, rules: PasteRule[] }): Plugin { export function pasteRulesPlugin(props: { editor: Editor, rules: PasteRule[] }): Plugin[] {
const { editor, rules } = props const { editor, rules } = props
let isProseMirrorHTML = false let isProseMirrorHTML = false
const plugin = new Plugin({ const plugins = rules.map(rule => {
props: { return new Plugin({
handlePaste: (view, event) => { props: {
const html = event.clipboardData?.getData('text/html') handlePaste: (view, event) => {
const html = event.clipboardData?.getData('text/html')
isProseMirrorHTML = !!html?.includes('data-pm-slice') isProseMirrorHTML = !!html?.includes('data-pm-slice')
return false return false
},
}, },
}, appendTransaction: (transactions, oldState, state) => {
appendTransaction: (transactions, oldState, state) => { const transaction = transactions[0]
const transaction = transactions[0]
// stop if there is not a paste event // stop if there is not a paste event
if (!transaction.getMeta('paste') || isProseMirrorHTML) { if (!transaction.getMeta('paste') || isProseMirrorHTML) {
return return
} }
// stop if there is no changed range // stop if there is no changed range
const { doc, before } = transaction const from = oldState.doc.content.findDiffStart(state.doc.content)
const from = before.content.findDiffStart(doc.content) const to = oldState.doc.content.findDiffEnd(state.doc.content)
const to = before.content.findDiffEnd(doc.content)
if (!isNumber(from) || !to || from === to.b) { if (!isNumber(from) || !to || from === to.b) {
return return
} }
// build a chainable state // build a chainable state
// so we can use a single transaction for all paste rules // so we can use a single transaction for all paste rules
const tr = state.tr const tr = state.tr
const chainableState = createChainableState({ const chainableState = createChainableState({
state, state,
transaction: tr, transaction: tr,
}) })
run({ const handler = run({
editor, editor,
state: chainableState, state: chainableState,
from: Math.max(from - 1, 0), from: Math.max(from - 1, 0),
to: to.b, to: to.b,
rules, rule,
plugin, })
})
// stop if there are no changes // stop if there are no changes
if (!tr.steps.length) { if (!handler || !tr.steps.length) {
return return
} }
return tr return tr
}, },
})
// @ts-ignore
isPasteRules: true,
}) })
return plugin return plugins
} }

View File

@@ -24,7 +24,7 @@ export function markInputRule(config: {
const attributes = callOrReturn(config.getAttributes, undefined, match) const attributes = callOrReturn(config.getAttributes, undefined, match)
if (attributes === false || attributes === null) { if (attributes === false || attributes === null) {
return return null
} }
const { tr } = state const { tr } = state
@@ -64,6 +64,8 @@ export function markInputRule(config: {
tr.removeStoredMark(config.type) tr.removeStoredMark(config.type)
} }
return tr
}, },
}) })
} }

View File

@@ -45,6 +45,8 @@ export function nodeInputRule(config: {
} else if (match[0]) { } else if (match[0]) {
tr.replaceWith(start, end, config.type.create(attributes)) tr.replaceWith(start, end, config.type.create(attributes))
} }
return tr
}, },
}) })
} }

View File

@@ -29,7 +29,11 @@ export function textInputRule(config: {
} }
} }
state.tr.insertText(insert, start, end) const { tr } = state
tr.insertText(insert, start, end)
return tr
}, },
}) })
} }

View File

@@ -29,9 +29,12 @@ export function textblockTypeInputRule(config: {
return null return null
} }
state.tr const { tr } = state
.delete(range.from, range.to)
tr.delete(range.from, range.to)
.setBlockType(range.from, range.from, config.type, attributes) .setBlockType(range.from, range.from, config.type, attributes)
return tr
}, },
}) })
} }

View File

@@ -54,6 +54,8 @@ export function wrappingInputRule(config: {
) { ) {
tr.join(range.from - 1) tr.join(range.from - 1)
} }
return tr
}, },
}) })
} }

View File

@@ -24,7 +24,7 @@ export function markPasteRule(config: {
const attributes = callOrReturn(config.getAttributes, undefined, match) const attributes = callOrReturn(config.getAttributes, undefined, match)
if (attributes === false || attributes === null) { if (attributes === false || attributes === null) {
return return null
} }
const { tr } = state const { tr } = state
@@ -64,6 +64,8 @@ export function markPasteRule(config: {
tr.removeStoredMark(config.type) tr.removeStoredMark(config.type)
} }
return tr
}, },
}) })
} }

View File

@@ -29,7 +29,11 @@ export function textPasteRule(config: {
} }
} }
state.tr.insertText(insert, start, end) const { tr } = state
tr.insertText(insert, start, end)
return tr
}, },
}) })
} }