diff --git a/packages/editor/core/src/ui/extensions/custom-link/helpers/clickHandler.ts b/packages/editor/core/src/ui/extensions/custom-link/helpers/clickHandler.ts index 0854092a9e4..ec6c540dacc 100644 --- a/packages/editor/core/src/ui/extensions/custom-link/helpers/clickHandler.ts +++ b/packages/editor/core/src/ui/extensions/custom-link/helpers/clickHandler.ts @@ -15,9 +15,15 @@ export function clickHandler(options: ClickHandlerOptions): Plugin { return false; } - const eventTarget = event.target as HTMLElement; + let a = event.target as HTMLElement; + const els = []; - if (eventTarget.nodeName !== "A") { + while (a.nodeName !== "DIV") { + els.push(a); + a = a.parentNode as HTMLElement; + } + + if (!els.find((value) => value.nodeName === "A")) { return false; } @@ -28,9 +34,7 @@ export function clickHandler(options: ClickHandlerOptions): Plugin { const target = link?.target ?? attrs.target; if (link && href) { - if (view.editable) { - window.open(href, target); - } + window.open(href, target); return true; } diff --git a/packages/editor/core/src/ui/extensions/custom-link/helpers/pasteHandler.ts b/packages/editor/core/src/ui/extensions/custom-link/helpers/pasteHandler.ts index 83e38054c74..475bf28d94b 100644 --- a/packages/editor/core/src/ui/extensions/custom-link/helpers/pasteHandler.ts +++ b/packages/editor/core/src/ui/extensions/custom-link/helpers/pasteHandler.ts @@ -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; diff --git a/packages/editor/core/src/ui/extensions/custom-link/index.tsx b/packages/editor/core/src/ui/extensions/custom-link/index.ts similarity index 69% rename from packages/editor/core/src/ui/extensions/custom-link/index.tsx rename to packages/editor/core/src/ui/extensions/custom-link/index.ts index e66d18904f8..88e7abfe57c 100644 --- a/packages/editor/core/src/ui/extensions/custom-link/index.tsx +++ b/packages/editor/core/src/ui/extensions/custom-link/index.ts @@ -1,41 +1,76 @@ -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"; - -import { autolink } from "src/ui/extensions/custom-link/helpers/autolink"; -import { clickHandler } from "src/ui/extensions/custom-link/helpers/clickHandler"; -import { pasteHandler } from "src/ui/extensions/custom-link/helpers/pasteHandler"; +import { autolink } from "./helpers/autolink"; +import { clickHandler } from "./helpers/clickHandler"; +import { pasteHandler } from "./helpers/pasteHandler"; export interface LinkProtocolOptions { scheme: string; 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. + */ autolink: boolean; - inclusive: boolean; + /** + * An array of custom protocols to be registered with linkifyjs. + */ protocols: Array; + /** + * If enabled, links will be opened on click. + */ openOnClick: boolean; + /** + * If enabled, links will be inclusive i.e. if you move your cursor to the + * link text, and start typing, it'll be a part of the link itself. + */ + inclusive: boolean; + /** + * Adds a link to the current selection if the pasted content only contains an url. + */ linkOnPaste: boolean; + /** + * A list of HTML attributes to be rendered. + */ HTMLAttributes: Record; + /** + * A validation function that modifies link verification for the auto linker. + * @param url - The url to be validated. + * @returns - True if the url is valid, false otherwise. + */ validate?: (url: string) => boolean; } declare module "@tiptap/core" { interface Commands { link: { + /** + * Set a link mark + */ setLink: (attributes: { href: string; target?: string | null; rel?: string | null; class?: string | null; }) => ReturnType; + /** + * Toggle a link mark + */ toggleLink: (attributes: { href: string; target?: string | null; rel?: string | null; class?: string | null; }) => ReturnType; + /** + * Unset a link mark + */ unsetLink: () => ReturnType; }; } @@ -150,37 +185,31 @@ export const CustomLinkExtension = Mark.create({ addPasteRules() { return [ markPasteRule({ - find: (text) => - find(text) - .filter((link) => { - if (this.options.validate) { - return this.options.validate(link.value); - } - 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 existingLink = html?.match(hrefRegex); - - if (existingLink) { - return { - href: existingLink[1], - }; + find: (text) => { + const foundLinks: PasteRuleMatch[] = []; + + 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 { - href: match.data?.href, - }; + return foundLinks; }, + type: this.type, + getAttributes: (match) => ({ + href: match.data?.href, + }), }), ]; },