Skip to content

Commit

Permalink
fix(richtext-lexical): improved regex matchers for absolute and relat…
Browse files Browse the repository at this point in the history
…ive URLs to make autolinking more reliable (#10725)

Fixes an issue where pasting text over a selection will automatically
add it as a link instead of replacing the text. This is caused by poor
regex matching in the case of relative URLs.

Added tests and strengthened both absolute and relative URL matchers
  • Loading branch information
paulpopus authored Jan 22, 2025
1 parent e6d0260 commit 9bb27af
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 4 deletions.
88 changes: 88 additions & 0 deletions packages/richtext-lexical/src/lexical/utils/url.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { jest } from '@jest/globals'
import { absoluteRegExp, relativeOrAnchorRegExp } from './url.js'

describe('Lexical URL Regex Matchers', () => {
describe('relative URLs', () => {
it('validation for links it should match', async () => {
const shouldMatch = [
'/path/to/resource',
'/file-name.html',
'/',
'/dir/',
'/path.with.dots/',
'#anchor',
'#section-title',
'/path#fragment',
]

shouldMatch.forEach((testCase) => {
expect(relativeOrAnchorRegExp.test(testCase)).toBe(true)
})
})

it('validation for links it should not match', async () => {
const shouldNotMatch = [
'match',
'http://example.com',
'relative/path',
'file.html',
'some#fragment',
'#',
'/#',
'/path/with spaces',
'',
'ftp://example.com',
]

shouldNotMatch.forEach((testCase) => {
expect(relativeOrAnchorRegExp.test(testCase)).not.toBe(true)
})
})
})

describe('absolute URLs', () => {
it('validation for links it should match', async () => {
const shouldMatch = [
'http://example.com',
'https://example.com',
'ftp://files.example.com',
'http://example.com/resource',
'https://example.com/resource?key=value',
'http://example.com/resource#anchor',
'http://www.example.com',
'https://sub.example.com/path/file',
'mailto:[email protected]',
'tel:+1234567890',
'http://user:[email protected]',
'www.example.com',
'www.example.com/resource',
'www.example.com/resource?query=1',
'www.example.com#fragment',
]

shouldMatch.forEach((testCase) => {
expect(absoluteRegExp.test(testCase)).toBe(true)
})
})

it('validation for links it should not match', async () => {
const shouldNotMatch = [
'/relative/path',
'#anchor',
'example.com',
'://missing.scheme',
'http://',
'http:/example.com',
'ftp://example .com',
'http://example',
'not-a-url',
'http//example.com',
'https://example.com/ spaces',
]

shouldNotMatch.forEach((testCase) => {
expect(absoluteRegExp.test(testCase)).not.toBe(true)
})
})
})
})
19 changes: 15 additions & 4 deletions packages/richtext-lexical/src/lexical/utils/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,28 @@ export function sanitizeUrl(url: string): string {
return 'https://'
}

// Source: https://stackoverflow.com/a/8234912/2013580
const absoluteRegExp =
/(?:[A-Za-z]{3,9}:(?:\/\/)?(?:[-;:&=+$,\w]+@)?[A-Za-z\d.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z\d.-]+)(?:\/[+~%/.\w-]*)?\??[-+=&;%@.\w]*#?\w*/
/**
* This regex checks for absolute URLs in a string. Tested for the following use cases:
* - http://example.com
* - https://example.com
* - ftp://files.example.com
* - http://example.com/resource
* - https://example.com/resource?key=value
* - http://example.com/resource#anchor
* - http://www.example.com
* - https://sub.example.com/path/file
* - mailto:
*/
export const absoluteRegExp =
/^(?:[a-zA-Z][a-zA-Z\d+.-]*:(?:\/\/)?(?:[-;:&=+$,\w]+@)?[A-Za-z\d]+(?:\.[A-Za-z\d]+)+|www\.[A-Za-z\d]+(?:\.[A-Za-z\d]+)+|(?:tel|mailto):[\w+.-]+)(?:\/[+~%/\w-]*)?(?:\?[-;&=%\w]*)?(?:#\w+)?$/

/**
* This regex checks for relative URLs starting with / or anchor links starting with # in a string. Tested for the following use cases:
* - /privacy-policy
* - /privacy-policy#primary-terms
* - #primary-terms
* */
const relativeOrAnchorRegExp = /^[\w\-./]*(?:#\w[\w-]*)?$/
export const relativeOrAnchorRegExp = /^(?:\/[\w\-./]*(?:#\w[\w-]*)?|#[\w\-]+)$/

/**
* Prevents unreasonable URLs from being inserted into the editor.
Expand Down

0 comments on commit 9bb27af

Please sign in to comment.