diff --git a/packages/tiptap-commands/package.json b/packages/tiptap-commands/package.json index af9682a9..d638f7c5 100644 --- a/packages/tiptap-commands/package.json +++ b/packages/tiptap-commands/package.json @@ -23,6 +23,7 @@ "prosemirror-commands": "^1.0.7", "prosemirror-inputrules": "^1.0.1", "prosemirror-schema-list": "^1.0.1", + "prosemirror-state": "^1.2.2", "tiptap-utils": "^1.0.1" } } diff --git a/packages/tiptap-commands/src/commands/wrappingPasteRule.js b/packages/tiptap-commands/src/commands/wrappingPasteRule.js new file mode 100644 index 00000000..47d4a3d4 --- /dev/null +++ b/packages/tiptap-commands/src/commands/wrappingPasteRule.js @@ -0,0 +1,52 @@ +import { Plugin } from 'prosemirror-state' +import { Slice, Fragment } from 'prosemirror-model' + +export default function (regexp, type, getAttrs) { + + const handler = fragment => { + const nodes = [] + + fragment.forEach(child => { + if (child.isText) { + const { text } = child + let pos = 0 + let match + + do { + match = regexp.exec(text) + if (match) { + const start = match.index + const end = start + match[0].length + const attrs = getAttrs instanceof Function ? getAttrs(match[0]) : getAttrs + + if (start > 0) { + nodes.push(child.cut(pos, start)) + } + + nodes.push(child + .cut(start, end) + .mark(type.create(attrs) + .addToSet(child.marks))) + + pos = end + } + } while (match) + + if (pos < text.length) { + nodes.push(child.cut(pos)) + } + } else { + nodes.push(child.copy(handler(child.content))) + } + }) + + return Fragment.fromArray(nodes) + } + + return new Plugin({ + props: { + transformPasted: slice => new Slice(handler(slice.content), slice.openStart, slice.openEnd), + }, + }) + +} diff --git a/packages/tiptap-commands/src/index.js b/packages/tiptap-commands/src/index.js index fb319aee..4c26bae8 100644 --- a/packages/tiptap-commands/src/index.js +++ b/packages/tiptap-commands/src/index.js @@ -48,6 +48,7 @@ import toggleBlockType from './commands/toggleBlockType' import toggleList from './commands/toggleList' import toggleWrap from './commands/toggleWrap' import updateMark from './commands/updateMark' +import wrappingPasteRule from './commands/wrappingPasteRule' export { // prosemirror-commands @@ -98,4 +99,5 @@ export { toggleList, toggleWrap, updateMark, + wrappingPasteRule, } diff --git a/packages/tiptap-extensions/src/marks/Link.js b/packages/tiptap-extensions/src/marks/Link.js index 00bfb638..77a2b28d 100644 --- a/packages/tiptap-extensions/src/marks/Link.js +++ b/packages/tiptap-extensions/src/marks/Link.js @@ -1,5 +1,5 @@ import { Mark, Plugin, TextSelection } from 'tiptap' -import { updateMark, removeMark } from 'tiptap-commands' +import { updateMark, removeMark, wrappingPasteRule } from 'tiptap-commands' import { getMarkRange } from 'tiptap-utils' export default class Link extends Mark { @@ -41,6 +41,16 @@ export default class Link extends Mark { } } + pasteRules({ type }) { + return [ + wrappingPasteRule( + /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/g, + type, + url => ({ href: url }), + ), + ] + } + get plugins() { return [ new Plugin({ diff --git a/packages/tiptap/src/Editor.js b/packages/tiptap/src/Editor.js index 7a92a24e..a345c7ec 100644 --- a/packages/tiptap/src/Editor.js +++ b/packages/tiptap/src/Editor.js @@ -25,6 +25,7 @@ export default class Editor { this.plugins = this.createPlugins() this.keymaps = this.createKeymaps() this.inputRules = this.createInputRules() + this.pasteRules = this.createPasteRules() this.state = this.createState() this.view = this.createView() this.commands = this.createCommands() @@ -94,6 +95,12 @@ export default class Editor { }) } + createPasteRules() { + return this.extensions.pasteRules({ + schema: this.schema, + }) + } + createCommands() { return this.extensions.commands({ schema: this.schema, @@ -126,6 +133,7 @@ export default class Editor { inputRules({ rules: this.inputRules, }), + ...this.pasteRules, ...this.keymaps, keymap({ Backspace: undoInputRule, diff --git a/packages/tiptap/src/Utils/ExtensionManager.js b/packages/tiptap/src/Utils/ExtensionManager.js index 65ed7c74..1e5ea55a 100644 --- a/packages/tiptap/src/Utils/ExtensionManager.js +++ b/packages/tiptap/src/Utils/ExtensionManager.js @@ -76,6 +76,29 @@ export default class ExtensionManager { ]), []) } + pasteRules({ schema }) { + const extensionPasteRules = this.extensions + .filter(extension => ['extension'].includes(extension.type)) + .filter(extension => extension.pasteRules) + .map(extension => extension.pasteRules({ schema })) + + const nodeMarkPasteRules = this.extensions + .filter(extension => ['node', 'mark'].includes(extension.type)) + .filter(extension => extension.pasteRules) + .map(extension => extension.pasteRules({ + type: schema[`${extension.type}s`][extension.name], + schema, + })) + + return [ + ...extensionPasteRules, + ...nodeMarkPasteRules, + ].reduce((allPasteRules, pasteRules) => ([ + ...allPasteRules, + ...pasteRules, + ]), []) + } + commands({ schema, view, editable }) { return this.extensions .filter(extension => extension.commands)