Skip to content

Commit

Permalink
[lexical][auto-link] Fix auto link node escapes on second "."
Browse files Browse the repository at this point in the history
  • Loading branch information
Maksym Plavinskyi authored and Maksym Plavinskyi committed May 20, 2024
1 parent 0bdd6fb commit 6b180fb
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 2 deletions.
133 changes: 133 additions & 0 deletions packages/lexical-playground/__tests__/e2e/AutoLinks.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -382,4 +382,137 @@ test.describe('Auto Links', () => {
{ignoreClasses: true},
);
});

test('Can convert URLs into links', async ({page, isPlainText}) => {
const testUrls = [
// Basic URLs
'http://example.com', // Standard HTTP URL
'https://example.com', // Standard HTTPS URL
'http://www.example.com', // HTTP URL with www
'https://www.example.com', // HTTPS URL with www
'www.example.com', // Missing HTTPS Protocol

// With Different TLDs
'http://example.org', // URL with .org TLD
'https://example.net', // URL with .net TLD
'http://example.co.uk', // URL with country code TLD
'https://example.xyz', // URL with generic TLD

// With Paths
'http://example.com/path/to/resource', // URL with path
'https://www.example.com/path/to/resource', // URL with www and path

// With Query Parameters
'http://example.com/path?name=value', // URL with query parameters
'https://www.example.com/path?name=value&another=value2', // URL with multiple query parameters

// With Fragments
'http://example.com/path#section', // URL with fragment
'https://www.example.com/path/to/resource#fragment', // URL with path and fragment

// With Port Numbers
'http://example.com:8080', // URL with port number
'https://www.example.com:443/path', // URL with port number and path

// IP Addresses
'http://192.168.0.1', // URL with IPv4 address
'https://127.0.0.1', // URL with localhost IPv4 address

// With Special Characters in Path and Query
'http://example.com/path/to/res+ource', // URL with plus in path
'https://example.com/path/to/res%20ource', // URL with encoded space in path
'http://example.com/path?name=va@lue', // URL with special character in query
'https://example.com/path?name=value&another=val%20ue', // URL with encoded space in query

// Subdomains and Uncommon TLDs
'http://subdomain.example.com', // URL with subdomain
'https://sub.subdomain.example.com', // URL with multiple subdomains
'http://example.museum', // URL with uncommon TLD
'https://example.travel', // URL with uncommon TLD

// Edge Cases
'http://foo.bar', // Minimal URL with uncommon TLD
'https://foo.bar', // HTTPS minimal URL with uncommon TLD
];

test.skip(isPlainText);
await focusEditor(page);
await page.keyboard.type(testUrls.join(' ') + ' ');

let expectedHTML = '';
for (let url of testUrls) {
url = url.replaceAll(/&/g, '&');
const rawUrl = url;

if (!url.startsWith('http')) {
url = `https://${url}`;
}

expectedHTML += `
<a href="${url}" dir="ltr">
<span data-lexical-text="true">${rawUrl}</span>
</a>
<span data-lexical-text="true"></span>
`;
}

await assertHTML(
page,
html`
<p dir="ltr">${expectedHTML}</p>
`,
undefined,
{ignoreClasses: true},
);
});

test(`Can not convert bad URLs into links`, async ({page, isPlainText}) => {
const testUrls = [
// Missing Protocol
'example.com', // Missing HTTPS and www

// Invalid Protocol
'htp://example.com', // Typo in protocol
'htps://example.com', // Typo in protocol

// Invalid TLDs
'http://example.abcdefg', // TLD too long

// Spaces and Invalid Characters
'http://exa mple.com', // Space in domain
'https://example .com', // Space in domain
'http://example!.com', // Invalid character in domain
'http://example.com.',
'http://example.com.-',

// Missing Domain
'http://.com', // Missing domain name
'https://.org', // Missing domain name

// Incomplete URLs
'http://', // Incomplete URL
'https://', // Incomplete URL

// Just Text
'not_a_url', // Plain text
'this is not a url', // Sentence
'example', // Single word
'ftp://example.com', // Unsupported protocol (assuming only HTTP/HTTPS is supported)
];

test.skip(isPlainText);
await focusEditor(page);
await page.keyboard.type(testUrls.join(' '));

await assertHTML(
page,
html`
<p dir="ltr">
<span data-lexical-text="true">${testUrls.join(' ')}</span>
</p>
`,
undefined,
{ignoreClasses: true},
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
import * as React from 'react';

const URL_REGEX =
/((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;
/((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)(?<![-.+():%])/;

const EMAIL_REGEX =
/(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/;
Expand Down
2 changes: 1 addition & 1 deletion packages/lexical-react/src/LexicalAutoLinkPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ function findFirstMatch(
return null;
}

const PUNCTUATION_OR_SPACE = /[.,;\s]/;
const PUNCTUATION_OR_SPACE = /[,;\s]/;

function isSeparator(char: string): boolean {
return PUNCTUATION_OR_SPACE.test(char);
Expand Down

0 comments on commit 6b180fb

Please sign in to comment.