import { Plugin } from 'prosemirror-state'; import { isSafeUrl } from '../sanitize'; const URL_PATTERN = /^https?:\/\/\S+$/i; export interface LinkPluginOptions { /** Called with the href when a link is Mod/Ctrl-clicked. */ onOpenLink?: (href: string) => void; } /** * Link behaviors that belong to the document, not the chrome: * - Mod/Ctrl-click opens the link. * - Pasting a bare URL over a text selection turns the selection into a link. */ export function linkPlugin(options: LinkPluginOptions = {}): Plugin { return new Plugin({ props: { handleClick(view, _pos, event) { if (!(event.metaKey || event.ctrlKey)) return false; const target = (event.target as HTMLElement | null)?.closest?.('a[href]'); if (!target) return false; const href = target.getAttribute('href')!; if (!isSafeUrl(href)) return false; if (options.onOpenLink) options.onOpenLink(href); else window.open(href, '_blank', 'noopener,noreferrer'); return true; }, handlePaste(view, event) { const { state } = view; const { empty, from, to } = state.selection; if (empty) return false; const text = event.clipboardData?.getData('text/plain')?.trim(); if (!text || !URL_PATTERN.test(text) || !isSafeUrl(text)) return false; const linkMark = state.schema.marks.link.create({ href: text }); view.dispatch(state.tr.addMark(from, to, linkMark)); return true; }, }, }); }