mirror of
https://github.com/facebook/lexical.git
synced 2025-08-06 16:39:33 +08:00
[lexical-markdown] Bug Fix: Link Transformer URL Protocol Handling (#7499)
This commit is contained in:

committed by
GitHub

parent
6c458e4289
commit
0aa2d5aa73
@ -37,6 +37,8 @@ import {
|
|||||||
TextNode,
|
TextNode,
|
||||||
} from 'lexical';
|
} from 'lexical';
|
||||||
|
|
||||||
|
import {formatUrl} from './utils';
|
||||||
|
|
||||||
export type Transformer =
|
export type Transformer =
|
||||||
| ElementTransformer
|
| ElementTransformer
|
||||||
| MultilineElementTransformer
|
| MultilineElementTransformer
|
||||||
@ -559,7 +561,8 @@ export const LINK: TextMatchTransformer = {
|
|||||||
/(?:\[([^[]+)\])(?:\((?:([^()\s]+)(?:\s"((?:[^"]*\\")*[^"]*)"\s*)?)\))$/,
|
/(?:\[([^[]+)\])(?:\((?:([^()\s]+)(?:\s"((?:[^"]*\\")*[^"]*)"\s*)?)\))$/,
|
||||||
replace: (textNode, match) => {
|
replace: (textNode, match) => {
|
||||||
const [, linkText, linkUrl, linkTitle] = match;
|
const [, linkText, linkUrl, linkTitle] = match;
|
||||||
const linkNode = $createLinkNode(linkUrl, {title: linkTitle});
|
const formattedUrl = formatUrl(linkUrl);
|
||||||
|
const linkNode = $createLinkNode(formattedUrl, {title: linkTitle});
|
||||||
const linkTextNode = $createTextNode(linkText);
|
const linkTextNode = $createTextNode(linkText);
|
||||||
linkTextNode.setFormat(textNode.getFormat());
|
linkTextNode.setFormat(textNode.getFormat());
|
||||||
linkNode.append(linkTextNode);
|
linkNode.append(linkTextNode);
|
||||||
|
@ -27,6 +27,7 @@ import {
|
|||||||
MultilineElementTransformer,
|
MultilineElementTransformer,
|
||||||
normalizeMarkdown,
|
normalizeMarkdown,
|
||||||
} from '../../MarkdownTransformers';
|
} from '../../MarkdownTransformers';
|
||||||
|
import {formatUrl} from '../../utils';
|
||||||
|
|
||||||
const SIMPLE_INLINE_JSX_MATCHER: TextMatchTransformer = {
|
const SIMPLE_INLINE_JSX_MATCHER: TextMatchTransformer = {
|
||||||
dependencies: [LinkNode],
|
dependencies: [LinkNode],
|
||||||
@ -888,3 +889,39 @@ E3
|
|||||||
expect(normalizeMarkdown(markdown, false)).toBe(markdown);
|
expect(normalizeMarkdown(markdown, false)).toBe(markdown);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('formatUrl', () => {
|
||||||
|
it('should not modify URLs with protocols', () => {
|
||||||
|
expect(formatUrl('https://example.com')).toBe('https://example.com');
|
||||||
|
expect(formatUrl('http://example.com')).toBe('http://example.com');
|
||||||
|
expect(formatUrl('mailto:user@example.com')).toBe(
|
||||||
|
'mailto:user@example.com',
|
||||||
|
);
|
||||||
|
expect(formatUrl('tel:+1234567890')).toBe('tel:+1234567890');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not modify relative paths', () => {
|
||||||
|
expect(formatUrl('/path/to/resource')).toBe('/path/to/resource');
|
||||||
|
expect(formatUrl('/index.html')).toBe('/index.html');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add mailto: to email addresses', () => {
|
||||||
|
expect(formatUrl('user@example.com')).toBe('mailto:user@example.com');
|
||||||
|
expect(formatUrl('name.lastname@domain.co.uk')).toBe(
|
||||||
|
'mailto:name.lastname@domain.co.uk',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add tel: to phone numbers', () => {
|
||||||
|
expect(formatUrl('+1234567890')).toBe('tel:+1234567890');
|
||||||
|
expect(formatUrl('123-456-7890')).toBe('tel:123-456-7890');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add https:// to URLs without protocols', () => {
|
||||||
|
expect(formatUrl('www.example.com')).toBe('https://www.example.com');
|
||||||
|
expect(formatUrl('example.com')).toBe('https://example.com');
|
||||||
|
expect(formatUrl('subdomain.example.com/path')).toBe(
|
||||||
|
'https://subdomain.example.com/path',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -460,3 +460,37 @@ export function isEmptyParagraph(node: LexicalNode): boolean {
|
|||||||
MARKDOWN_EMPTY_LINE_REG_EXP.test(firstChild.getTextContent()))
|
MARKDOWN_EMPTY_LINE_REG_EXP.test(firstChild.getTextContent()))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const PHONE_NUMBER_REGEX = /^\+?[0-9\s()-]{5,}$/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a URL string by adding appropriate protocol if missing
|
||||||
|
*
|
||||||
|
* @param url - URL to format
|
||||||
|
* @returns Formatted URL with appropriate protocol
|
||||||
|
*/
|
||||||
|
export function formatUrl(url: string): string {
|
||||||
|
// Check if URL already has a protocol
|
||||||
|
if (url.match(/^[a-z][a-z0-9+.-]*:/i)) {
|
||||||
|
// URL already has a protocol, leave it as is
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
// Check if it's a relative path (starting with '/', '.', or '#')
|
||||||
|
else if (url.match(/^[/#.]/)) {
|
||||||
|
// Relative path, leave it as is
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for email address
|
||||||
|
else if (url.includes('@')) {
|
||||||
|
return `mailto:${url}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for phone number
|
||||||
|
else if (PHONE_NUMBER_REGEX.test(url)) {
|
||||||
|
return `tel:${url}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For everything else, return with https:// prefix
|
||||||
|
return `https://${url}`;
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user