autolink improvement

This commit is contained in:
Jakub Kolčář 2023-09-06 13:59:57 +02:00 committed by Jan Thurau
parent 43a6a865c8
commit d9cdf1e1c1
No known key found for this signature in database
GPG Key ID: 60B3EB3A1C49AC04

View File

@ -3,15 +3,23 @@ import {
findChildrenInRange, findChildrenInRange,
getChangedRanges, getChangedRanges,
getMarksBetween, getMarksBetween,
NodeWithPos,
} from '@tiptap/core' } from '@tiptap/core'
import { MarkType } from '@tiptap/pm/model' import { MarkType } from '@tiptap/pm/model'
import { Plugin, PluginKey } from '@tiptap/pm/state' import { Plugin, PluginKey } from '@tiptap/pm/state'
import { find } from 'linkifyjs' import { find } from 'linkifyjs'
type AutolinkOptions = { type AutolinkOptions = {
type: MarkType type: MarkType;
validate?: (url: string) => boolean validate?: (url: string) => boolean;
}
type LinkType = {
type: string;
value: string;
isLink: boolean;
href: string;
start: number;
end: number;
} }
export function autolink(options: AutolinkOptions): Plugin { export function autolink(options: AutolinkOptions): Plugin {
@ -37,40 +45,34 @@ export function autolink(options: AutolinkOptions): Plugin {
node => node.isTextblock, node => node.isTextblock,
) )
let textBlock: NodeWithPos | undefined // Grab the first node within the changed ranges (ex. the first of two paragraphs when hitting enter).
let textBeforeWhitespace: string | undefined const textBlock = nodesInChangedRanges[0]
if (nodesInChangedRanges.length > 1) { if (!textBlock) {
// Grab the first node within the changed ranges (ex. the first of two paragraphs when hitting enter). return
textBlock = nodesInChangedRanges[0]
textBeforeWhitespace = newState.doc.textBetween(
textBlock.pos,
textBlock.pos + textBlock.node.nodeSize,
undefined,
' ',
)
} else if (
nodesInChangedRanges.length
// 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]
textBeforeWhitespace = newState.doc.textBetween(
textBlock.pos,
newRange.to,
undefined,
' ',
)
} }
if (textBlock && textBeforeWhitespace) { const textBeforeWhitespace = newState.doc.textBetween(
const wordsBeforeWhitespace = textBeforeWhitespace.split(' ').filter(s => s !== '') textBlock.pos,
newRange.to,
' ',
' ',
)
if (wordsBeforeWhitespace.length <= 0) { if (textBeforeWhitespace) {
return false
if (!/\s*\S+\s+$/gm.test(textBeforeWhitespace)) {
return
} }
const lastWordBeforeSpace = wordsBeforeWhitespace[wordsBeforeWhitespace.length - 1] const lastWordBeforeSpaceMatch = textBeforeWhitespace.matchAll(/\s*(\S+)\s+$/gm)
const lastMatch = Array.from(lastWordBeforeSpaceMatch).at(-1)
if (lastMatch === undefined || lastMatch.length < 2) {
return
}
const lastWordBeforeSpace = lastMatch[1]
const lastWordAndBlockOffset = textBlock.pos + textBeforeWhitespace.lastIndexOf(lastWordBeforeSpace) const lastWordAndBlockOffset = textBlock.pos + textBeforeWhitespace.lastIndexOf(lastWordBeforeSpace)
if (!lastWordBeforeSpace) { if (!lastWordBeforeSpace) {
@ -78,22 +80,22 @@ export function autolink(options: AutolinkOptions): Plugin {
} }
find(lastWordBeforeSpace) find(lastWordBeforeSpace)
.filter(link => link.isLink) .filter((link: LinkType) => link.isLink)
// Calculate link position. // Calculate link position.
.map(link => ({ .map((link: LinkType) => ({
...link, ...link,
from: lastWordAndBlockOffset + link.start + 1, start: lastWordAndBlockOffset + link.start + 1,
to: lastWordAndBlockOffset + link.end + 1, end: lastWordAndBlockOffset + link.end + 1,
})) }))
// ignore link inside code mark // ignore link inside code mark
.filter(link => { .filter((link: LinkType) => {
if (!newState.schema.marks.code) { if (!newState.schema.marks.code) {
return true return true
} }
return !newState.doc.rangeHasMark( return !newState.doc.rangeHasMark(
link.from, link.start,
link.to, link.end,
newState.schema.marks.code, newState.schema.marks.code,
) )
}) })
@ -106,13 +108,13 @@ export function autolink(options: AutolinkOptions): Plugin {
}) })
// Add link mark. // Add link mark.
.forEach(link => { .forEach(link => {
if (getMarksBetween(link.from, link.to, newState.doc).some(item => item.mark.type === options.type)) { if (getMarksBetween(link.start, link.end, newState.doc).some(item => item.mark.type === options.type)) {
return return
} }
tr.addMark( tr.addMark(
link.from, link.start,
link.to, link.end,
options.type.create({ options.type.create({
href: link.href, href: link.href,
}), }),