Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: Autolink plugin URL recognition failures #5275

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
96 changes: 96 additions & 0 deletions packages/lexical-playground/__tests__/e2e/AutoLinks.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -307,4 +307,100 @@ test.describe('Auto Links', () => {
{ignoreClasses: true},
);
});

test(`Does not convert bad URLs into links`, async ({page, isPlainText}) => {
const badUrls = [
'http://',
'http://.',
'http://..',
'http://../',
'http://?',
'http://??',
'http://??/',
'http://#',
'http://##',
'http://##/',
'//',
'//a',
'///a',
'///',
'http:///a',
'rdar://1234',
'h://test',
':// should fail',
'http://foo.bar/foo(bar)baz quux',
'http://-error-.invalid/',
'http://-a.b.co',
'http://a.b-.co',
'http://ex..ample.com',
'http://example..com',
'http://example-.com',
'http://-example.com',
];

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

await assertHTML(
page,
html`
<p dir="ltr">
<span data-lexical-text="true">${badUrls.join(' ')}</span>
</p>
`,
undefined,
{ignoreClasses: true},
);
});

test('Does convert good complex URLs into links', async ({
page,
isPlainText,
}) => {
const goodUrls = [
'http://foo.com/blah_blah',
'http://foo.com/blah_blah/',
'http://www.example.com/wpstyle/?p=364',
'https://www.example.com/foo/?bar=baz&inga=42&quux',
'http://foo.com/something?after=parens',
'http://jlo.mp',
'http://1337.net',
'http://a.b-c.de',
// // Include IPs and localhost
'http://localhost',
'http://localhost:3000',
'http://192.168.1.1',
'http://192.168.1.1:3000',
'http://example.com',
'http://example.com/path/to/resource?query=string#fragment',
'https://username:password@example.com',
'http://example.com/path/to/page.html?query=string#fragment',
'https://example.com#anchor',
'http://abcdefghij.com',
];

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

let expectedHTML = '';
for (const url of goodUrls) {
expectedHTML += `
<a href="${url.replaceAll(/&/g, '&amp;')}" dir="ltr">
<span data-lexical-text="true">${url.replace(/&/g, '&amp;')}</span>
</a>
<span data-lexical-text="true"></span>
`;
}

await assertHTML(
page,
html`
<p dir="ltr">${expectedHTML}</p>
`,
undefined,
{ignoreClasses: true},
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +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?:\/\/)?((\w+:\w+@)?(([a-zA-Z\d]([a-zA-Z\d-]*[a-zA-Z\d])*)\.)+[a-zA-Z]{2,}|localhost|(\d{1,3}\.){3}\d{1,3})(:\d+)?(\/[-a-zA-Z\d%_.~+]*)*(\?[;&a-zA-Z\d%_.~+=-]*)?(#[-a-zA-Z\d_]*)?/;
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 @@ -73,7 +73,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