diff --git a/packages/core/src/ExtensionManager.ts b/packages/core/src/ExtensionManager.ts index ae7155bf3e..10399000ab 100644 --- a/packages/core/src/ExtensionManager.ts +++ b/packages/core/src/ExtensionManager.ts @@ -306,7 +306,7 @@ export class ExtensionManager { editor, rules: inputRules, }), - pasteRulesPlugin({ + ...pasteRulesPlugin({ editor, rules: pasteRules, }), diff --git a/packages/core/src/InputRule.ts b/packages/core/src/InputRule.ts index 88f18905f2..2a943871b8 100644 --- a/packages/core/src/InputRule.ts +++ b/packages/core/src/InputRule.ts @@ -1,4 +1,9 @@ -import { EditorState, Plugin, TextSelection } from 'prosemirror-state' +import { + EditorState, + Plugin, + TextSelection, + Transaction, +} from 'prosemirror-state' import { Editor } from './Editor' import { CommandManager } from './CommandManager' import { createChainableState } from './helpers/createChainableState' @@ -33,7 +38,7 @@ export class InputRule { commands: SingleCommands, chain: () => ChainedCommands, can: () => CanCommands, - }) => void + }) => Transaction | null constructor(config: { find: InputRuleFinder, @@ -44,7 +49,7 @@ export class InputRule { commands: SingleCommands, chain: () => ChainedCommands, can: () => CanCommands, - }) => void, + }) => Transaction | null, }) { this.find = config.find this.handler = config.handler @@ -87,7 +92,7 @@ function run(config: { text: string, rules: InputRule[], plugin: Plugin, -}): any { +}): boolean { const { editor, from, @@ -148,7 +153,7 @@ function run(config: { state, }) - rule.handler({ + const handler = rule.handler({ state, range, match, @@ -158,7 +163,7 @@ function run(config: { }) // stop if there are no changes - if (!tr.steps.length) { + if (!handler || !tr.steps.length) { return } diff --git a/packages/core/src/PasteRule.ts b/packages/core/src/PasteRule.ts index c14f0e889c..b220de1a5c 100644 --- a/packages/core/src/PasteRule.ts +++ b/packages/core/src/PasteRule.ts @@ -1,4 +1,4 @@ -import { EditorState, Plugin } from 'prosemirror-state' +import { EditorState, Plugin, Transaction } from 'prosemirror-state' import { Editor } from './Editor' import { CommandManager } from './CommandManager' import { createChainableState } from './helpers/createChainableState' @@ -34,7 +34,7 @@ export class PasteRule { commands: SingleCommands, chain: () => ChainedCommands, can: () => CanCommands, - }) => void + }) => Transaction | null constructor(config: { find: PasteRuleFinder, @@ -45,7 +45,7 @@ export class PasteRule { commands: SingleCommands, chain: () => ChainedCommands, can: () => CanCommands, - }) => void, + }) => Transaction | null, }) { this.find = config.find this.handler = config.handler @@ -88,15 +88,14 @@ function run(config: { state: EditorState, from: number, to: number, - rules: PasteRule[], - plugin: Plugin, -}): any { + rule: PasteRule, +}): boolean { const { editor, state, from, to, - rules, + rule, } = config const { commands, chain, can } = new CommandManager({ @@ -104,6 +103,8 @@ function run(config: { state, }) + const handlers: (Transaction | null)[] = [] + state.doc.nodesBetween(from, to, (node, pos) => { if (!node.isTextblock || node.type.spec.code) { return @@ -118,32 +119,36 @@ function run(config: { '\ufffc', ) - rules.forEach(rule => { - const matches = pasteRuleMatcherHandler(textToMatch, rule.find) + const matches = pasteRuleMatcherHandler(textToMatch, rule.find) - matches.forEach(match => { - if (match.index === undefined) { - return - } + matches.forEach(match => { + if (match.index === undefined) { + return + } - const start = resolvedFrom + match.index + 1 - const end = start + match[0].length - const range = { - from: state.tr.mapping.map(start), - to: state.tr.mapping.map(end), - } + const start = resolvedFrom + match.index + 1 + 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, - commands, - chain, - can, - }) + const handler = rule.handler({ + state, + range, + match, + commands, + chain, + 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 rule’s * action. */ -export function pasteRulesPlugin(props: { editor: Editor, rules: PasteRule[] }): Plugin { +export function pasteRulesPlugin(props: { editor: Editor, rules: PasteRule[] }): Plugin[] { const { editor, rules } = props let isProseMirrorHTML = false - const plugin = new Plugin({ - props: { - handlePaste: (view, event) => { - const html = event.clipboardData?.getData('text/html') + const plugins = rules.map(rule => { + return new Plugin({ + props: { + 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) => { - const transaction = transactions[0] + appendTransaction: (transactions, oldState, state) => { + const transaction = transactions[0] - // stop if there is not a paste event - if (!transaction.getMeta('paste') || isProseMirrorHTML) { - 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) + // stop if there is not a paste event + if (!transaction.getMeta('paste') || isProseMirrorHTML) { + return + } - if (!isNumber(from) || !to || from === to.b) { - return - } + // stop if there is no changed range + const from = oldState.doc.content.findDiffStart(state.doc.content) + const to = oldState.doc.content.findDiffEnd(state.doc.content) - // 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, - }) + if (!isNumber(from) || !to || from === to.b) { + return + } - run({ - editor, - state: chainableState, - from: Math.max(from - 1, 0), - to: to.b, - rules, - plugin, - }) + // 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, + }) - // stop if there are no changes - if (!tr.steps.length) { - return - } + const handler = run({ + editor, + state: chainableState, + from: Math.max(from - 1, 0), + to: to.b, + rule, + }) - return tr - }, + // stop if there are no changes + if (!handler || !tr.steps.length) { + return + } - // @ts-ignore - isPasteRules: true, + return tr + }, + }) }) - return plugin + return plugins } diff --git a/packages/core/src/inputRules/markInputRule.ts b/packages/core/src/inputRules/markInputRule.ts index 91663e34a9..b01b63bb1e 100644 --- a/packages/core/src/inputRules/markInputRule.ts +++ b/packages/core/src/inputRules/markInputRule.ts @@ -24,7 +24,7 @@ export function markInputRule(config: { const attributes = callOrReturn(config.getAttributes, undefined, match) if (attributes === false || attributes === null) { - return + return null } const { tr } = state @@ -64,6 +64,8 @@ export function markInputRule(config: { tr.removeStoredMark(config.type) } + + return tr }, }) } diff --git a/packages/core/src/inputRules/nodeInputRule.ts b/packages/core/src/inputRules/nodeInputRule.ts index 0dc14da4e8..c80cb917e1 100644 --- a/packages/core/src/inputRules/nodeInputRule.ts +++ b/packages/core/src/inputRules/nodeInputRule.ts @@ -45,6 +45,8 @@ export function nodeInputRule(config: { } else if (match[0]) { tr.replaceWith(start, end, config.type.create(attributes)) } + + return tr }, }) } diff --git a/packages/core/src/inputRules/textInputRule.ts b/packages/core/src/inputRules/textInputRule.ts index 75414b0391..bfc5e137af 100644 --- a/packages/core/src/inputRules/textInputRule.ts +++ b/packages/core/src/inputRules/textInputRule.ts @@ -29,7 +29,11 @@ export function textInputRule(config: { } } - state.tr.insertText(insert, start, end) + const { tr } = state + + tr.insertText(insert, start, end) + + return tr }, }) } diff --git a/packages/core/src/inputRules/textblockTypeInputRule.ts b/packages/core/src/inputRules/textblockTypeInputRule.ts index f879082d7a..79ca14a79b 100644 --- a/packages/core/src/inputRules/textblockTypeInputRule.ts +++ b/packages/core/src/inputRules/textblockTypeInputRule.ts @@ -29,9 +29,12 @@ export function textblockTypeInputRule(config: { return null } - state.tr - .delete(range.from, range.to) + const { tr } = state + + tr.delete(range.from, range.to) .setBlockType(range.from, range.from, config.type, attributes) + + return tr }, }) } diff --git a/packages/core/src/inputRules/wrappingInputRule.ts b/packages/core/src/inputRules/wrappingInputRule.ts index ba540e6aa1..b6e3002fbb 100644 --- a/packages/core/src/inputRules/wrappingInputRule.ts +++ b/packages/core/src/inputRules/wrappingInputRule.ts @@ -54,6 +54,8 @@ export function wrappingInputRule(config: { ) { tr.join(range.from - 1) } + + return tr }, }) } diff --git a/packages/core/src/pasteRules/markPasteRule.ts b/packages/core/src/pasteRules/markPasteRule.ts index 6c0c9b1fc3..9dee95841a 100644 --- a/packages/core/src/pasteRules/markPasteRule.ts +++ b/packages/core/src/pasteRules/markPasteRule.ts @@ -24,7 +24,7 @@ export function markPasteRule(config: { const attributes = callOrReturn(config.getAttributes, undefined, match) if (attributes === false || attributes === null) { - return + return null } const { tr } = state @@ -64,6 +64,8 @@ export function markPasteRule(config: { tr.removeStoredMark(config.type) } + + return tr }, }) } diff --git a/packages/core/src/pasteRules/textPasteRule.ts b/packages/core/src/pasteRules/textPasteRule.ts index 6e0d94c34b..e82b2cc779 100644 --- a/packages/core/src/pasteRules/textPasteRule.ts +++ b/packages/core/src/pasteRules/textPasteRule.ts @@ -29,7 +29,11 @@ export function textPasteRule(config: { } } - state.tr.insertText(insert, start, end) + const { tr } = state + + tr.insertText(insert, start, end) + + return tr }, }) }