mirror of
https://github.com/ueberdosis/tiptap.git
synced 2025-01-19 06:43:02 +08:00
Fix/link pasting (#4700)
* revert link paste handling to behavior before * fix(link): fix linking while typing * add validate support for autolinking back * revert autolink behaviour * fix autolinking on pasting text * remove broken link * fix react link test * fix savvy test --------- Co-authored-by: bdbch <dominik@bdbch.com>
This commit is contained in:
parent
5aa9051881
commit
eaee9c7177
@ -24,7 +24,7 @@ context('/src/Examples/Savvy/React/', () => {
|
||||
|
||||
tests.forEach(test => {
|
||||
it(`should parse ${test[0]} correctly`, () => {
|
||||
cy.get('.tiptap').type(test[0]).should('contain', test[1])
|
||||
cy.get('.tiptap').type(`${test[0]} `).should('contain', test[1])
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -25,7 +25,7 @@ context('/src/Examples/Savvy/Vue/', () => {
|
||||
tests.forEach(test => {
|
||||
it(`should parse ${test[0]} correctly`, () => {
|
||||
cy.get('.tiptap')
|
||||
.type(test[0])
|
||||
.type(`${test[0]} `)
|
||||
.should('contain', test[1])
|
||||
})
|
||||
})
|
||||
|
@ -17,6 +17,7 @@ export default () => {
|
||||
Code,
|
||||
Link.configure({
|
||||
openOnClick: false,
|
||||
autolink: true,
|
||||
}),
|
||||
],
|
||||
content: `
|
||||
|
@ -1,5 +1,3 @@
|
||||
import Link from '@tiptap/extension-link'
|
||||
|
||||
context('/src/Marks/Link/React/', () => {
|
||||
before(() => {
|
||||
cy.visit('/src/Marks/Link/React/')
|
||||
@ -12,18 +10,6 @@ context('/src/Marks/Link/React/', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should add a custom class to a link', () => {
|
||||
const linkExtension = Link.configure({
|
||||
HTMLAttributes: {
|
||||
class: 'foo',
|
||||
},
|
||||
})
|
||||
|
||||
expect(linkExtension.options.HTMLAttributes).to.deep.include({
|
||||
class: 'foo',
|
||||
})
|
||||
})
|
||||
|
||||
it('should parse a tags correctly', () => {
|
||||
cy.get('.tiptap').then(([{ editor }]) => {
|
||||
editor.commands.setContent('<p><a href="#">Example Text</a></p>')
|
||||
@ -66,6 +52,16 @@ context('/src/Marks/Link/React/', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('detects autolinking', () => {
|
||||
cy.get('.tiptap').type('https://example.com ').find('a').should('contain', 'https://example.com')
|
||||
.should('have.attr', 'href', 'https://example.com')
|
||||
})
|
||||
|
||||
it('detects autolinking with numbers', () => {
|
||||
cy.get('.tiptap').type('https://tiptap4u.com ').find('a').should('contain', 'https://tiptap4u.com')
|
||||
.should('have.attr', 'href', 'https://tiptap4u.com')
|
||||
})
|
||||
|
||||
it('detects a pasted URL within a text', () => {
|
||||
cy.get('.tiptap')
|
||||
.paste({
|
||||
|
@ -61,6 +61,16 @@ context('/src/Marks/Link/Vue/', () => {
|
||||
.should('have.attr', 'href', 'https://example.com')
|
||||
})
|
||||
|
||||
it('detects autolinking', () => {
|
||||
cy.get('.tiptap').type('https://example.com ').find('a').should('contain', 'https://example.com')
|
||||
.should('have.attr', 'href', 'https://example.com')
|
||||
})
|
||||
|
||||
it('detects autolinking with numbers', () => {
|
||||
cy.get('.tiptap').type('https://tiptap4u.com ').find('a').should('contain', 'https://tiptap4u.com')
|
||||
.should('have.attr', 'href', 'https://tiptap4u.com')
|
||||
})
|
||||
|
||||
it('detects a pasted URL with query params', () => {
|
||||
cy.get('.tiptap')
|
||||
.paste({ pastePayload: 'https://example.com?paramA=nice¶mB=cool', pasteType: 'text/plain' })
|
||||
|
@ -21,7 +21,7 @@ export type PasteRuleMatch = {
|
||||
data?: Record<string, any>
|
||||
}
|
||||
|
||||
export type PasteRuleFinder = RegExp | ((text: string) => PasteRuleMatch[] | null | undefined)
|
||||
export type PasteRuleFinder = RegExp | ((text: string, event?: ClipboardEvent) => PasteRuleMatch[] | null | undefined)
|
||||
|
||||
export class PasteRule {
|
||||
find: PasteRuleFinder
|
||||
@ -58,12 +58,13 @@ export class PasteRule {
|
||||
const pasteRuleMatcherHandler = (
|
||||
text: string,
|
||||
find: PasteRuleFinder,
|
||||
event?: ClipboardEvent,
|
||||
): ExtendedRegExpMatchArray[] => {
|
||||
if (isRegExp(find)) {
|
||||
return [...text.matchAll(find)]
|
||||
}
|
||||
|
||||
const matches = find(text)
|
||||
const matches = find(text, event)
|
||||
|
||||
if (!matches) {
|
||||
return []
|
||||
@ -119,7 +120,7 @@ function run(config: {
|
||||
const resolvedTo = Math.min(to, pos + node.content.size)
|
||||
const textToMatch = node.textBetween(resolvedFrom - pos, resolvedTo - pos, undefined, '\ufffc')
|
||||
|
||||
const matches = pasteRuleMatcherHandler(textToMatch, rule.find)
|
||||
const matches = pasteRuleMatcherHandler(textToMatch, rule.find, pasteEvent)
|
||||
|
||||
matches.forEach(match => {
|
||||
if (match.index === undefined) {
|
||||
|
@ -26,6 +26,10 @@ export function getMarksBetween(from: number, to: number, doc: ProseMirrorNode):
|
||||
})
|
||||
} else {
|
||||
doc.nodesBetween(from, to, (node, pos) => {
|
||||
if (!node || node.nodeSize === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
marks.push(
|
||||
...node.marks.map(mark => ({
|
||||
from: pos,
|
||||
|
@ -33,16 +33,8 @@ export function pasteHandler(options: PasteHandlerOptions): Plugin {
|
||||
return false
|
||||
}
|
||||
|
||||
const html = event.clipboardData?.getData('text/html')
|
||||
|
||||
const hrefRegex = /href="([^"]*)"/
|
||||
|
||||
const existingLink = html?.match(hrefRegex)
|
||||
|
||||
const url = existingLink ? existingLink[1] : link.href
|
||||
|
||||
options.editor.commands.setMark(options.type, {
|
||||
href: url,
|
||||
href: link.href,
|
||||
})
|
||||
|
||||
return true
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { Mark, markPasteRule, mergeAttributes } from '@tiptap/core'
|
||||
import {
|
||||
Mark, markPasteRule, mergeAttributes, PasteRuleMatch,
|
||||
} from '@tiptap/core'
|
||||
import { Plugin } from '@tiptap/pm/state'
|
||||
import { find, registerCustomProtocol, reset } from 'linkifyjs'
|
||||
|
||||
@ -11,6 +13,8 @@ export interface LinkProtocolOptions {
|
||||
optionalSlashes?: boolean;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
export interface LinkOptions {
|
||||
/**
|
||||
* If enabled, it adds links as you type.
|
||||
@ -158,33 +162,46 @@ export const Link = Mark.create<LinkOptions>({
|
||||
addPasteRules() {
|
||||
return [
|
||||
markPasteRule({
|
||||
find: text => find(text)
|
||||
.filter(link => {
|
||||
if (this.options.validate) {
|
||||
return this.options.validate(link.value)
|
||||
}
|
||||
find: (text, event) => {
|
||||
const html = event?.clipboardData?.getData('text/html')
|
||||
|
||||
return true
|
||||
})
|
||||
.filter(link => link.isLink)
|
||||
.map(link => ({
|
||||
text: link.value,
|
||||
index: link.start,
|
||||
data: link,
|
||||
})),
|
||||
type: this.type,
|
||||
getAttributes: (match, pasteEvent) => {
|
||||
const html = pasteEvent?.clipboardData?.getData('text/html')
|
||||
const hrefRegex = /href="([^"]*)"/
|
||||
const foundLinks: PasteRuleMatch[] = []
|
||||
|
||||
const existingLink = html?.match(hrefRegex)
|
||||
if (html) {
|
||||
const dom = new DOMParser().parseFromString(html, 'text/html')
|
||||
const anchors = dom.querySelectorAll('a')
|
||||
|
||||
if (existingLink) {
|
||||
return {
|
||||
href: existingLink[1],
|
||||
if (anchors.length) {
|
||||
[...anchors].forEach(anchor => (foundLinks.push({
|
||||
text: anchor.innerText,
|
||||
data: {
|
||||
href: anchor.getAttribute('href'),
|
||||
},
|
||||
// get the index of the anchor inside the text
|
||||
// and add the length of the anchor text
|
||||
index: dom.body.innerText.indexOf(anchor.innerText) + anchor.innerText.length,
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
if (text) {
|
||||
const links = find(text).filter(item => item.isLink)
|
||||
|
||||
if (links.length) {
|
||||
links.forEach(link => (foundLinks.push({
|
||||
text: link.value,
|
||||
data: {
|
||||
href: link.href,
|
||||
},
|
||||
index: link.start,
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
return foundLinks
|
||||
},
|
||||
type: this.type,
|
||||
getAttributes: match => {
|
||||
return {
|
||||
href: match.data?.href,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user