/**
* This plugin allows for the creation of standalone links in the editor.
* It handles the pasting of URLs and the conversion of selected text to links.
*/
function createStandaloneLinkPlugin() {
return new Plugin({
props: {
handlePaste(view, event, slice) {
event.preventDefault()
const urlRegex =
/^(?:https?:\/\/)?(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_+.~#?&/=]*)$/
const httpRegex = /^https?:\/\//
let tr = view.state.tr
const { empty, from, to } = tr.selection
let handled = false
// Check if pasted content is a single URL
const pastedText = slice.content.textBetween(
0,
slice.content.size,
'\n',
)
const isUrl = urlRegex.test(pastedText)
if (isUrl && !empty) {
// If there's a selection and pasted content is a URL, convert selection to link
const url = httpRegex.test(pastedText)
? pastedText
: `http://${pastedText}`
const linkMark = view.state.schema?.marks?.['link']?.create({
href: url,
})
if (linkMark) {
tr = tr.addMark(from, to, linkMark)
}
handled = true
} else {
// this part will create a link if you paste a url without a selection
tr = tr.replaceSelection(slice)
const insertPos = from
slice.content.nodesBetween(0, slice.content.size, (node, nodePos) => {
if (node.isText && node.text) {
const match = node.text.match(urlRegex)?.[0]
if (match) {
handled = true
const url = httpRegex.test(match) ? match : `http://${match}`
const linkFrom =
insertPos + nodePos + node.text.indexOf(match) - 1
const linkTo = linkFrom + match.length
const linkMark = view.state.schema?.marks?.['link']?.create({
href: url,
})
if (linkMark) {
tr = tr.addMark(linkFrom, linkTo, linkMark)
}
}
}
})
}
if (handled) {
view.dispatch(tr)
return true
}
return false
},
},
})
}
I just made this plugin to make standalone links and an existing text node into a link.
Also, if you paste a URL in the editor the handlePaste function will handle it and convert it to a link text/ A tag in HTML
you also need a link attribute in you’re schema for this plugin to work
I am providing the link atri/markSpec to help start with the schema building feel free to edit it.
link: {
attrs: {
href: { validate: 'string' },
title: { default: null, validate: 'string|null' },
},
inclusive: false,
parseDOM: [
{
tag: 'a[href]',
getAttrs(dom: HTMLElement) {
return {
href: dom.getAttribute('href'),
title: dom.getAttribute('title'),
}
},
},
],
toDOM(node) {
const { href, title } = node.attrs
return ['a', { href, title, target: '_blank' }, 0]
},
} as MarkSpec,
And here is a bonus Plugin that works with the above plugin that will show the URL below the link on hover
/**
* This plugin underlines links on hover and logs their URLs to a tooltip.
*/
function createLinkHoverPlugin() {
let tooltip: HTMLElement | null = null
let hoveredLink: HTMLAnchorElement | null = null
function createTooltip() {
const el = document.createElement('div')
//Use your own class name or use object.assign and assign the inline style directly here
// I am using my customer tailwind class name
el.className =
'link-tooltip text-primary text-size-label bg-fill absolute outline-inner outline-default rounded-md p-xs z-[9999] hidden'
document.body.appendChild(el)
return el
}
function updateTooltip(view: EditorView, href: string, pos: number) {
if (!tooltip) tooltip = createTooltip()
tooltip.textContent = href
setTimeout(() => {
if (tooltip) tooltip.style.display = 'block'
}, 500)
const rect = view.coordsAtPos(pos)
tooltip.style.left = `${rect.left}px`
tooltip.style.top = `${rect.bottom + 3}px`
}
function hideTooltip() {
if (tooltip) tooltip.style.display = 'none'
}
function addUnderline(element: HTMLAnchorElement) {
element.style.textDecoration = 'underline'
}
function removeUnderline(element: HTMLAnchorElement) {
element.style.textDecoration = ''
}
return new Plugin({
view() {
return {
destroy() {
if (tooltip) {
document.body.removeChild(tooltip)
tooltip = null
}
if (hoveredLink) {
removeUnderline(hoveredLink)
hoveredLink = null
}
},
}
},
props: {
handleDOMEvents: {
mouseover(view, event) {
if (!(event.target instanceof HTMLAnchorElement)) return false
const pos = view.posAtDOM(event.target, 0)
if (pos === null) return false
if (event.target.tagName === 'A') {
const link = event.target.href
if (link) {
updateTooltip(view, link, pos)
addUnderline(event.target)
hoveredLink = event.target
} else {
hideTooltip()
}
}
return false
},
mouseout() {
hideTooltip()
if (hoveredLink) {
removeUnderline(hoveredLink)
hoveredLink = null
}
return false
},
},
},
})
}
I hope this helps someone