diff --git a/package.json b/package.json index 02734bd2bb..ab7f35e56d 100644 --- a/package.json +++ b/package.json @@ -89,10 +89,10 @@ "is-ip": "^3.1.0", "jszip": "^3.7.0", "katex": "^0.16.0", - "linkify-element": "4.0.0-beta.4", - "linkify-react": "4.0.0-beta.4", - "linkify-string": "4.0.0-beta.4", - "linkifyjs": "4.0.0-beta.4", + "linkify-element": "4.1.1", + "linkify-react": "4.1.1", + "linkify-string": "4.1.1", + "linkifyjs": "4.1.1", "lodash": "^4.17.20", "maplibre-gl": "^2.0.0", "matrix-encrypt-attachment": "^1.0.3", diff --git a/src/linkify-matrix.ts b/src/linkify-matrix.ts index 085143f198..400a3ba153 100644 --- a/src/linkify-matrix.ts +++ b/src/linkify-matrix.ts @@ -38,17 +38,14 @@ export enum Type { RoomAlias = "roomalias", } -// Linkify stuff doesn't type scanner/parser/utils properly :/ function matrixOpaqueIdLinkifyParser({ scanner, parser, - utils, token, name, }: { - scanner: any; - parser: any; - utils: any; + scanner: linkifyjs.ScannerInit; + parser: linkifyjs.ParserInit; token: "#" | "+" | "@"; name: Type; }): void { @@ -56,54 +53,48 @@ function matrixOpaqueIdLinkifyParser({ DOT, // IPV4 necessity NUM, - TLD, COLON, SYM, SLASH, EQUALS, HYPHEN, UNDERSCORE, - // because 'localhost' is tokenised to the localhost token, - // usernames @localhost:foo.com are otherwise not matched! - LOCALHOST, - domain, } = scanner.tokens; - const S_START = parser.start; - const matrixSymbol = utils.createTokenClass(name, { isLink: true }); + // Contains NUM, WORD, UWORD, EMOJI, TLD, UTLD, SCHEME, SLASH_SCHEME and LOCALHOST plus custom protocols (e.g. "matrix") + const { domain } = scanner.tokens.groups; - const localpartTokens = [domain, TLD, DOT, LOCALHOST, SYM, SLASH, EQUALS, UNDERSCORE, HYPHEN]; - const domainpartTokens = [domain, TLD, LOCALHOST, HYPHEN]; + // Tokens we need that are not contained in the domain group + const additionalLocalpartTokens = [DOT, SYM, SLASH, EQUALS, UNDERSCORE, HYPHEN]; + const additionalDomainpartTokens = [HYPHEN]; - const INITIAL_STATE = S_START.tt(token); + const matrixToken = linkifyjs.createTokenClass(name, { isLink: true }); + const matrixTokenState = new linkifyjs.State(matrixToken) as any as linkifyjs.State; // linkify doesn't appear to type this correctly - const LOCALPART_STATE = INITIAL_STATE.tt(domain); - for (const token of localpartTokens) { - INITIAL_STATE.tt(token, LOCALPART_STATE); - LOCALPART_STATE.tt(token, LOCALPART_STATE); - } - const LOCALPART_STATE_DOT = LOCALPART_STATE.tt(DOT); - for (const token of localpartTokens) { - LOCALPART_STATE_DOT.tt(token, LOCALPART_STATE); - } + const matrixTokenWithPort = linkifyjs.createTokenClass(name, { isLink: true }); + const matrixTokenWithPortState = new linkifyjs.State( + matrixTokenWithPort, + ) as any as linkifyjs.State; // linkify doesn't appear to type this correctly + const INITIAL_STATE = parser.start.tt(token); + + // Localpart + const LOCALPART_STATE = new linkifyjs.State(); + INITIAL_STATE.ta(domain, LOCALPART_STATE); + INITIAL_STATE.ta(additionalLocalpartTokens, LOCALPART_STATE); + LOCALPART_STATE.ta(domain, LOCALPART_STATE); + LOCALPART_STATE.ta(additionalLocalpartTokens, LOCALPART_STATE); + + // Domainpart const DOMAINPART_STATE_DOT = LOCALPART_STATE.tt(COLON); - const DOMAINPART_STATE = DOMAINPART_STATE_DOT.tt(domain); - DOMAINPART_STATE.tt(DOT, DOMAINPART_STATE_DOT); - for (const token of domainpartTokens) { - DOMAINPART_STATE.tt(token, DOMAINPART_STATE); - // we are done if we have a domain - DOMAINPART_STATE.tt(token, matrixSymbol); - } + DOMAINPART_STATE_DOT.ta(domain, matrixTokenState); + DOMAINPART_STATE_DOT.ta(additionalDomainpartTokens, matrixTokenState); + matrixTokenState.ta(domain, matrixTokenState); + matrixTokenState.ta(additionalDomainpartTokens, matrixTokenState); + matrixTokenState.tt(DOT, DOMAINPART_STATE_DOT); - // accept repeated TLDs (e.g .org.uk) but do not accept double dots: .. - for (const token of domainpartTokens) { - DOMAINPART_STATE_DOT.tt(token, DOMAINPART_STATE); - } - - const PORT_STATE = DOMAINPART_STATE.tt(COLON); - - PORT_STATE.tt(NUM, matrixSymbol); + // Port suffixes + matrixTokenState.tt(COLON).tt(NUM, matrixTokenWithPortState); } function onUserClick(event: MouseEvent, userId: string): void { @@ -231,23 +222,21 @@ export const options: Opts = { }; // Run the plugins -registerPlugin(Type.RoomAlias, ({ scanner, parser, utils }: any) => { +registerPlugin(Type.RoomAlias, ({ scanner, parser }) => { const token = scanner.tokens.POUND as "#"; matrixOpaqueIdLinkifyParser({ scanner, parser, - utils, token, name: Type.RoomAlias, }); }); -registerPlugin(Type.UserId, ({ scanner, parser, utils }: any) => { +registerPlugin(Type.UserId, ({ scanner, parser }) => { const token = scanner.tokens.AT as "@"; matrixOpaqueIdLinkifyParser({ scanner, parser, - utils, token, name: Type.UserId, }); diff --git a/test/linkify-matrix-test.ts b/test/linkify-matrix-test.ts index 22694da9db..4fd2166c2b 100644 --- a/test/linkify-matrix-test.ts +++ b/test/linkify-matrix-test.ts @@ -138,6 +138,20 @@ describe("linkify-matrix", () => { }, ]); }); + it("properly parses " + char + "localhost:foo.com", () => { + const test = char + "localhost:foo.com"; + const found = linkify.find(test); + expect(found).toEqual([ + { + href: char + "localhost:foo.com", + type, + value: char + "localhost:foo.com", + start: 0, + end: test.length, + isLink: true, + }, + ]); + }); it("properly parses " + char + "foo:localhost", () => { const test = char + "foo:localhost"; const found = linkify.find(test); @@ -162,7 +176,6 @@ describe("linkify-matrix", () => { value: char + "foo:bar.com", start: 0, end: test.length, - isLink: true, }, ]); @@ -219,7 +232,6 @@ describe("linkify-matrix", () => { href: char + "foo:bar.com", start: 0, end: test.length - ":".length, - isLink: true, }, ]); @@ -238,6 +250,20 @@ describe("linkify-matrix", () => { }, ]); }); + it("ignores duplicate :NUM (double port specifier)", () => { + const test = "" + char + "foo:bar.com:2225:1234"; + const found = linkify.find(test); + expect(found).toEqual([ + { + href: char + "foo:bar.com:2225", + type, + value: char + "foo:bar.com:2225", + start: 0, + end: 17, + isLink: true, + }, + ]); + }); it("ignores all the trailing :", () => { const test = "" + char + "foo:bar.com::::"; const found = linkify.find(test); @@ -262,7 +288,6 @@ describe("linkify-matrix", () => { value: char + "foo.asdf:bar.com", start: 0, end: test.length - ":".repeat(4).length, - isLink: true, }, ]); @@ -281,7 +306,7 @@ describe("linkify-matrix", () => { }, ]); }); - it("does not parse multiple room aliases in one string", () => { + it("properly parses room alias with hyphen in domain part", () => { const test = "" + char + "foo:bar.com-baz.com"; const found = linkify.find(test); expect(found).toEqual([ diff --git a/yarn.lock b/yarn.lock index 0971e782f0..d8a45cc695 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6333,25 +6333,25 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== -linkify-element@4.0.0-beta.4: - version "4.0.0-beta.4" - resolved "https://registry.yarnpkg.com/linkify-element/-/linkify-element-4.0.0-beta.4.tgz#31bb5dff7430c4debc34030466bd8f3e297793a7" - integrity sha512-dsu5qxk6MhQHxXUlPjul33JknQPx7Iv/N8zisH4JtV31qVk0qZg/5gn10Hr76GlMuixcdcxVvGHNfVcvbut13w== +linkify-element@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/linkify-element/-/linkify-element-4.1.1.tgz#049221d53250e67c053cd94dd0ef411cccb87b28" + integrity sha512-G//YNU6WXu1uo/oneLfGE6UPlz5cdk4M43l+WHPezdWUQ/B703g9CtvxtLgfNFU8a/9+c9XjI+d+vfQTiH+KHg== -linkify-react@4.0.0-beta.4: - version "4.0.0-beta.4" - resolved "https://registry.yarnpkg.com/linkify-react/-/linkify-react-4.0.0-beta.4.tgz#75311ade523a52d43054dd841d724d746d43f60d" - integrity sha512-o4vFe28vtk6i8a6tbtkLyusIyhLJSYoHC3gEpmJEVqi6Hy3aguVEenYmtaOjmAQehDrBYeHv9s4qcneZOf7SWQ== +linkify-react@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/linkify-react/-/linkify-react-4.1.1.tgz#79cc29c6e5c0fd660be74a6a51d25c1b36977cf7" + integrity sha512-2K9Y1cUdvq40dFWqCJ//X+WP19nlzIVITFGI93RjLnA0M7KbnxQ/ffC3AZIZaEIrLangF9Hjt3i0GQ9/anEG5A== -linkify-string@4.0.0-beta.4: - version "4.0.0-beta.4" - resolved "https://registry.yarnpkg.com/linkify-string/-/linkify-string-4.0.0-beta.4.tgz#0982509bc6ce81c554bff8d7121057193b84ea32" - integrity sha512-1U90tclSloCMAhbcuu4S+BN7ZisZkFB6ggKS1ofdYy1bmtgxdXGDppVUV+qRp5rcAudla7K0LBgOiwCQ0WzrYQ== +linkify-string@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/linkify-string/-/linkify-string-4.1.1.tgz#461eb30b66752dec21f3557ebe55983ae3f5b195" + integrity sha512-9+kj8xr7GLiyNyO9ri7lIxq2ixVYjjqvtomPQpeYNNT56/PxQq6utzXFLm8HxOaGTiMpimj1UAQWwYYPV88L1g== -linkifyjs@4.0.0-beta.4: - version "4.0.0-beta.4" - resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.0.0-beta.4.tgz#8a03e7a999ed0b578a14d690585a32706525c45e" - integrity sha512-j8IUYMqyTT0aDrrkA5kf4hn6QurSKjGiQbqjNr4qc8dwEXIniCGp0JrdXmsGcTOEyhKG03GyRnJjp3NDTBBPDQ== +linkifyjs@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.1.1.tgz#73d427e3bbaaf4ca8e71c589ad4ffda11a9a5fde" + integrity sha512-zFN/CTVmbcVef+WaDXT63dNzzkfRBKT1j464NJQkV7iSgJU0sLBus9W0HBwnXK13/hf168pbrx/V/bjEHOXNHA== listr2@^3.8.3: version "3.14.0"