diff --git a/.vscode/settings.json b/.vscode/settings.json
index f89d258263c..77c9a5dc7b8 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -60,5 +60,8 @@
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
"[typescript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
+ },
+ "[javascript]": {
+ "editor.defaultFormatter": "dbaeumer.vscode-eslint"
}
}
diff --git a/demos/src/Marks/Link/React/index.spec.js b/demos/src/Marks/Link/React/index.spec.js
index db43732b5a0..a34a7c9e99f 100644
--- a/demos/src/Marks/Link/React/index.spec.js
+++ b/demos/src/Marks/Link/React/index.spec.js
@@ -71,6 +71,14 @@ context('/src/Marks/Link/React/', () => {
.should('have.attr', 'href', 'https://example.com')
})
+ it('detects a pasted URL with query params', () => {
+ cy.get('.ProseMirror')
+ .paste({ pastePayload: 'https://example.com?paramA=nice¶mB=cool', pasteType: 'text/plain' })
+ .find('a')
+ .should('contain', 'Example Text')
+ .should('have.attr', 'href', 'https://example.com?paramA=nice¶mB=cool')
+ })
+
it('correctly detects multiple pasted URLs', () => {
cy.get('.ProseMirror').paste({
pastePayload:
diff --git a/demos/src/Marks/Link/Vue/index.spec.js b/demos/src/Marks/Link/Vue/index.spec.js
index 7f80fb60550..a3b33016599 100644
--- a/demos/src/Marks/Link/Vue/index.spec.js
+++ b/demos/src/Marks/Link/Vue/index.spec.js
@@ -61,6 +61,14 @@ context('/src/Marks/Link/Vue/', () => {
.should('have.attr', 'href', 'https://example.com')
})
+ it('detects a pasted URL with query params', () => {
+ cy.get('.ProseMirror')
+ .paste({ pastePayload: 'https://example.com?paramA=nice¶mB=cool', pasteType: 'text/plain' })
+ .find('a')
+ .should('contain', 'Example Text')
+ .should('have.attr', 'href', 'https://example.com?paramA=nice¶mB=cool')
+ })
+
it('correctly detects multiple pasted URLs', () => {
cy.get('.ProseMirror').paste({ pastePayload: 'https://example1.com, https://example2.com/foobar, (http://example3.com/foobar)', pasteType: 'text/plain' })
diff --git a/packages/extension-link/src/helpers/autolink.ts b/packages/extension-link/src/helpers/autolink.ts
index 123a610c29b..643a76f0a61 100644
--- a/packages/extension-link/src/helpers/autolink.ts
+++ b/packages/extension-link/src/helpers/autolink.ts
@@ -29,6 +29,7 @@ export function autolink(options: AutolinkOptions): Plugin {
const transform = combineTransactionSteps(oldState.doc, [...transactions])
const { mapping } = transform
const changes = getChangedRanges(transform)
+ let needsAutolink = true
changes.forEach(({ oldRange, newRange }) => {
// at first we check if we have to remove links
@@ -51,13 +52,21 @@ export function autolink(options: AutolinkOptions): Plugin {
const wasLink = test(oldLinkText)
const isLink = test(newLinkText)
+ if (wasLink) {
+ needsAutolink = false
+ }
+
// remove only the link, if it was a link before too
// because we don’t want to remove links that were set manually
if (wasLink && !isLink) {
- tr.removeMark(newMark.from, newMark.to, options.type)
+ tr.removeMark(needsAutolink ? newMark.from : newMark.to - 1, newMark.to, options.type)
}
})
+ if (!needsAutolink) {
+ return
+ }
+
// now let’s see if we can add new links
const nodesInChangedRanges = findChildrenInRange(
newState.doc,
diff --git a/packages/extension-link/src/helpers/pasteHandler.ts b/packages/extension-link/src/helpers/pasteHandler.ts
index ed03e6903a1..0d4c834032c 100644
--- a/packages/extension-link/src/helpers/pasteHandler.ts
+++ b/packages/extension-link/src/helpers/pasteHandler.ts
@@ -1,11 +1,12 @@
import { Editor } from '@tiptap/core'
-import { MarkType } from '@tiptap/pm/model'
+import { Mark, MarkType } from '@tiptap/pm/model'
import { Plugin, PluginKey } from '@tiptap/pm/state'
import { find } from 'linkifyjs'
type PasteHandlerOptions = {
editor: Editor
type: MarkType
+ linkOnPaste?: boolean
}
export function pasteHandler(options: PasteHandlerOptions): Plugin {
@@ -15,11 +16,18 @@ export function pasteHandler(options: PasteHandlerOptions): Plugin {
handlePaste: (view, event, slice) => {
const { state } = view
const { selection } = state
- const { empty } = selection
- if (empty) {
- return false
- }
+ const pastedLinkMarks: Mark[] = []
+
+ slice.content.forEach(node => {
+ node.marks.forEach(mark => {
+ if (mark.type.name === options.type.name) {
+ pastedLinkMarks.push(mark)
+ }
+ })
+ })
+
+ const hasPastedLink = pastedLinkMarks.length > 0
let textContent = ''
@@ -29,15 +37,65 @@ export function pasteHandler(options: PasteHandlerOptions): Plugin {
const link = find(textContent).find(item => item.isLink && item.value === textContent)
- if (!textContent || !link) {
+ if (!selection.empty && options.linkOnPaste) {
+ const pastedLink = hasPastedLink ? pastedLinkMarks[0].attrs.href : link?.href || null
+
+ if (pastedLink) {
+ options.editor.commands.setMark(options.type, {
+ href: pastedLink,
+ })
+ return true
+ }
+ }
+
+ if (slice.content.firstChild?.type.name === 'text' && slice.content.firstChild?.marks.some(mark => mark.type.name === options.type.name)) {
return false
}
- options.editor.commands.setMark(options.type, {
- href: link.href,
+ if (link && selection.empty) {
+ options.editor.commands.insertContent(`${link.href}`)
+ return true
+ }
+
+ const { tr } = state
+ let deleteOnly = false
+
+ if (!selection.empty) {
+ deleteOnly = true
+ tr.delete(selection.from, selection.to)
+ }
+
+ let currentPos = selection.from
+
+ slice.content.forEach(node => {
+ const fragmentLinks = find(node.textContent)
+
+ tr.insert(currentPos - 1, node)
+
+ if (fragmentLinks.length > 0) {
+ deleteOnly = false
+
+ fragmentLinks.forEach(fragmentLink => {
+ const linkStart = currentPos + fragmentLink.start
+ const linkEnd = currentPos + fragmentLink.end
+
+ const hasMark = tr.doc.rangeHasMark(linkStart, linkEnd, options.type)
+
+ if (!hasMark) {
+ tr.addMark(linkStart, linkEnd, options.type.create({ href: fragmentLink.href }))
+ }
+ })
+
+ }
+ currentPos += node.nodeSize
})
- return true
+ if (tr.docChanged && !deleteOnly) {
+ options.editor.view.dispatch(tr)
+ return true
+ }
+
+ return false
},
},
})
diff --git a/packages/extension-link/src/link.ts b/packages/extension-link/src/link.ts
index e7898dea132..dde70e53e9d 100644
--- a/packages/extension-link/src/link.ts
+++ b/packages/extension-link/src/link.ts
@@ -1,6 +1,6 @@
-import { Mark, markPasteRule, mergeAttributes } from '@tiptap/core'
+import { Mark, mergeAttributes } from '@tiptap/core'
import { Plugin } from '@tiptap/pm/state'
-import { find, registerCustomProtocol, reset } from 'linkifyjs'
+import { registerCustomProtocol, reset } from 'linkifyjs'
import { autolink } from './helpers/autolink'
import { clickHandler } from './helpers/clickHandler'
@@ -146,31 +146,6 @@ export const Link = Mark.create({
}
},
- addPasteRules() {
- return [
- markPasteRule({
- find: text => find(text)
- .filter(link => {
- if (this.options.validate) {
- return this.options.validate(link.value)
- }
-
- return true
- })
- .filter(link => link.isLink)
- .map(link => ({
- text: link.value,
- index: link.start,
- data: link,
- })),
- type: this.type,
- getAttributes: match => ({
- href: match.data?.href,
- }),
- }),
- ]
- },
-
addProseMirrorPlugins() {
const plugins: Plugin[] = []
@@ -191,14 +166,13 @@ export const Link = Mark.create({
)
}
- if (this.options.linkOnPaste) {
- plugins.push(
- pasteHandler({
- editor: this.editor,
- type: this.type,
- }),
- )
- }
+ plugins.push(
+ pasteHandler({
+ editor: this.editor,
+ type: this.type,
+ linkOnPaste: this.options.linkOnPaste,
+ }),
+ )
return plugins
},