[WEB-617] fix: link behaviour fixed on formatting (#3855)

* fix: link behaviour fixed on formatting

* chore: added harmful script checks for links
This commit is contained in:
M. Palanikannan 2024-03-06 14:22:02 +05:30 committed by GitHub
parent 87eadc3c5d
commit 126d01bdc5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 71 additions and 46 deletions

View File

@ -15,9 +15,15 @@ export function clickHandler(options: ClickHandlerOptions): Plugin {
return false; 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; return false;
} }
@ -28,9 +34,7 @@ export function clickHandler(options: ClickHandlerOptions): Plugin {
const target = link?.target ?? attrs.target; const target = link?.target ?? attrs.target;
if (link && href) { if (link && href) {
if (view.editable) { window.open(href, target);
window.open(href, target);
}
return true; return true;
} }

View File

@ -33,16 +33,8 @@ export function pasteHandler(options: PasteHandlerOptions): Plugin {
return false; 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, { options.editor.commands.setMark(options.type, {
href: url, href: link.href,
}); });
return true; return true;

View File

@ -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 { Plugin } from "@tiptap/pm/state";
import { find, registerCustomProtocol, reset } from "linkifyjs"; import { find, registerCustomProtocol, reset } from "linkifyjs";
import { autolink } from "./helpers/autolink";
import { autolink } from "src/ui/extensions/custom-link/helpers/autolink"; import { clickHandler } from "./helpers/clickHandler";
import { clickHandler } from "src/ui/extensions/custom-link/helpers/clickHandler"; import { pasteHandler } from "./helpers/pasteHandler";
import { pasteHandler } from "src/ui/extensions/custom-link/helpers/pasteHandler";
export interface LinkProtocolOptions { export interface LinkProtocolOptions {
scheme: string; scheme: string;
optionalSlashes?: boolean; 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 { export interface LinkOptions {
/**
* If enabled, it adds links as you type.
*/
autolink: boolean; autolink: boolean;
inclusive: boolean; /**
* An array of custom protocols to be registered with linkifyjs.
*/
protocols: Array<LinkProtocolOptions | string>; protocols: Array<LinkProtocolOptions | string>;
/**
* If enabled, links will be opened on click.
*/
openOnClick: boolean; 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; linkOnPaste: boolean;
/**
* A list of HTML attributes to be rendered.
*/
HTMLAttributes: Record<string, any>; HTMLAttributes: Record<string, any>;
/**
* 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; validate?: (url: string) => boolean;
} }
declare module "@tiptap/core" { declare module "@tiptap/core" {
interface Commands<ReturnType> { interface Commands<ReturnType> {
link: { link: {
/**
* Set a link mark
*/
setLink: (attributes: { setLink: (attributes: {
href: string; href: string;
target?: string | null; target?: string | null;
rel?: string | null; rel?: string | null;
class?: string | null; class?: string | null;
}) => ReturnType; }) => ReturnType;
/**
* Toggle a link mark
*/
toggleLink: (attributes: { toggleLink: (attributes: {
href: string; href: string;
target?: string | null; target?: string | null;
rel?: string | null; rel?: string | null;
class?: string | null; class?: string | null;
}) => ReturnType; }) => ReturnType;
/**
* Unset a link mark
*/
unsetLink: () => ReturnType; unsetLink: () => ReturnType;
}; };
} }
@ -150,37 +185,31 @@ export const CustomLinkExtension = Mark.create<LinkOptions>({
addPasteRules() { addPasteRules() {
return [ return [
markPasteRule({ markPasteRule({
find: (text) => find: (text) => {
find(text) const foundLinks: PasteRuleMatch[] = [];
.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 (text) {
const links = find(text).filter((item) => item.isLink);
if (existingLink) { if (links.length) {
return { links.forEach((link) =>
href: existingLink[1], foundLinks.push({
}; text: link.value,
data: {
href: link.href,
},
index: link.start,
})
);
}
} }
return { return foundLinks;
href: match.data?.href,
};
}, },
type: this.type,
getAttributes: (match) => ({
href: match.data?.href,
}),
}), }),
]; ];
}, },