From d19267ecefabf08e4bd27c52424ed83991ce7270 Mon Sep 17 00:00:00 2001 From: Sven Adlung Date: Thu, 25 May 2023 13:47:27 +0200 Subject: [PATCH] fix(extension-link): fix paste handling * do not dispatch transaction without any links getting pasted * prevent onPaste handling in code blocks --- .../extension-link/src/helpers/autolink.ts | 16 ++++----- .../src/helpers/pasteHandler.ts | 36 +++++++++++-------- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/packages/extension-link/src/helpers/autolink.ts b/packages/extension-link/src/helpers/autolink.ts index 3e0dfaee5..9e00f5756 100644 --- a/packages/extension-link/src/helpers/autolink.ts +++ b/packages/extension-link/src/helpers/autolink.ts @@ -32,7 +32,7 @@ export function autolink(options: AutolinkOptions): Plugin { let needsAutolink = true changes.forEach(({ oldRange, newRange }) => { - // at first we check if we have to remove links + // At first we check if we have to remove links. getMarksBetween(oldRange.from, oldRange.to, oldState.doc) .filter(item => item.mark.type === options.type) .forEach(oldMark => { @@ -56,14 +56,14 @@ export function autolink(options: AutolinkOptions): Plugin { 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 + // 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(needsAutolink ? newMark.from : newMark.to - 1, newMark.to, options.type) } }) - // now let’s see if we can add new links + // Now let’s see if we can add new links. const nodesInChangedRanges = findChildrenInRange( newState.doc, newRange, @@ -74,7 +74,7 @@ export function autolink(options: AutolinkOptions): Plugin { let textBeforeWhitespace: string | undefined if (nodesInChangedRanges.length > 1) { - // Grab the first node within the changed ranges (ex. the first of two paragraphs when hitting enter) + // Grab the first node within the changed ranges (ex. the first of two paragraphs when hitting enter). textBlock = nodesInChangedRanges[0] textBeforeWhitespace = newState.doc.textBetween( textBlock.pos, @@ -84,7 +84,7 @@ export function autolink(options: AutolinkOptions): Plugin { ) } else if ( nodesInChangedRanges.length - // We want to make sure to include the block seperator argument to treat hard breaks like spaces + // We want to make sure to include the block seperator argument to treat hard breaks like spaces. && newState.doc.textBetween(newRange.from, newRange.to, ' ', ' ').endsWith(' ') ) { textBlock = nodesInChangedRanges[0] @@ -118,13 +118,13 @@ export function autolink(options: AutolinkOptions): Plugin { } return true }) - // calculate link position + // Calculate link position. .map(link => ({ ...link, from: lastWordAndBlockOffset + link.start + 1, to: lastWordAndBlockOffset + link.end + 1, })) - // add link mark + // Add link mark. .forEach(link => { if (getMarksBetween(link.from, link.to, newState.doc).some(item => item.mark.type === options.type)) { return diff --git a/packages/extension-link/src/helpers/pasteHandler.ts b/packages/extension-link/src/helpers/pasteHandler.ts index 0d4c83403..1cb0f87ad 100644 --- a/packages/extension-link/src/helpers/pasteHandler.ts +++ b/packages/extension-link/src/helpers/pasteHandler.ts @@ -17,9 +17,17 @@ export function pasteHandler(options: PasteHandlerOptions): Plugin { const { state } = view const { selection } = state + // Do not proceed if in code block. + if (state.doc.resolve(selection.from).parent.type.spec.code) { + return false + } + const pastedLinkMarks: Mark[] = [] + let textContent = '' slice.content.forEach(node => { + textContent += node.textContent + node.marks.forEach(mark => { if (mark.type.name === options.type.name) { pastedLinkMarks.push(mark) @@ -28,32 +36,28 @@ export function pasteHandler(options: PasteHandlerOptions): Plugin { }) const hasPastedLink = pastedLinkMarks.length > 0 - - let textContent = '' - - slice.content.forEach(node => { - textContent += node.textContent - }) - const link = find(textContent).find(item => item.isLink && item.value === textContent) 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, - }) + 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)) { + const firstChildIsText = slice.content.firstChild?.type.name === 'text' + const firstChildContainsLinkMark = slice.content.firstChild?.marks.some(mark => mark.type.name === options.type.name) + + if (firstChildIsText && firstChildContainsLinkMark) { return false } if (link && selection.empty) { options.editor.commands.insertContent(`${link.href}`) + return true } @@ -62,13 +66,15 @@ export function pasteHandler(options: PasteHandlerOptions): Plugin { if (!selection.empty) { deleteOnly = true + tr.delete(selection.from, selection.to) } let currentPos = selection.from + let fragmentLinks = [] slice.content.forEach(node => { - const fragmentLinks = find(node.textContent) + fragmentLinks = find(node.textContent) tr.insert(currentPos - 1, node) @@ -78,7 +84,6 @@ export function pasteHandler(options: PasteHandlerOptions): Plugin { fragmentLinks.forEach(fragmentLink => { const linkStart = currentPos + fragmentLink.start const linkEnd = currentPos + fragmentLink.end - const hasMark = tr.doc.rangeHasMark(linkStart, linkEnd, options.type) if (!hasMark) { @@ -90,8 +95,11 @@ export function pasteHandler(options: PasteHandlerOptions): Plugin { currentPos += node.nodeSize }) - if (tr.docChanged && !deleteOnly) { + const hasFragmentLinks = fragmentLinks.length > 0 + + if (tr.docChanged && !deleteOnly && hasFragmentLinks) { options.editor.view.dispatch(tr) + return true }