typescriptifying linkify and regex

This commit is contained in:
Isaiah Becker-Mayer 2022-09-18 21:56:05 -04:00 committed by Isaiah Becker-Mayer
parent 664038b946
commit c8e6ca9a83
4 changed files with 25 additions and 25 deletions

View File

@ -1,4 +1,4 @@
import { linkify } from "./linkify/linkify.js"; import { linkify } from "./linkify/linkify";
import { getIdentifierColorNumber, avatarInitials } from "../../../avatar"; import { getIdentifierColorNumber, avatarInitials } from "../../../avatar";
/** /**

View File

@ -15,7 +15,7 @@ limitations under the License.
*/ */
import { MessageBody, HeaderBlock, TableBlock, ListBlock, CodeBlock, PillPart, FormatPart, NewLinePart, RulePart, TextPart, LinkPart, ImagePart } from "./MessageBody.js" import { MessageBody, HeaderBlock, TableBlock, ListBlock, CodeBlock, PillPart, FormatPart, NewLinePart, RulePart, TextPart, LinkPart, ImagePart } from "./MessageBody.js"
import { linkify } from "./linkify/linkify.js"; import {linkify} from "./linkify/linkify";
/* At the time of writing (Jul 1 2021), Matrix Spec recommends /* At the time of writing (Jul 1 2021), Matrix Spec recommends
* allowing the following HTML tags: * allowing the following HTML tags:

View File

@ -21,10 +21,8 @@ import { regex } from "./regex.js";
* For each such separated token, callback is called * For each such separated token, callback is called
* with the token and a boolean passed as argument. * with the token and a boolean passed as argument.
* The boolean indicates whether the token is a link or not. * The boolean indicates whether the token is a link or not.
* @param {string} text Text to split
* @param {function(string, boolean)} callback A function to call with split tokens
*/ */
export function linkify(text, callback) { export function linkify(text: string, callback: (token: string, isLink: boolean) => void): void {
const matches = text.matchAll(regex); const matches = text.matchAll(regex);
let curr = 0; let curr = 0;
for (let match of matches) { for (let match of matches) {
@ -32,16 +30,18 @@ export function linkify(text, callback) {
callback(precedingText, false); callback(precedingText, false);
callback(match[0], true); callback(match[0], true);
const len = match[0].length; const len = match[0].length;
curr = match.index + len; curr = match.index! + len;
} }
const remainingText = text.slice(curr); const remainingText = text.slice(curr);
callback(remainingText, false); callback(remainingText, false);
} }
export function tests() { export function tests(): any {
class MockCallback { class MockCallback {
mockCallback(text, isLink) { result: { type: "link" | "text", text: string }[];
mockCallback(text: string, isLink: boolean): void {
if (!text.length) { if (!text.length) {
return; return;
} }
@ -53,13 +53,13 @@ export function tests() {
} }
} }
function test(assert, input, output) { function test(assert, input, output): void {
const m = new MockCallback; const m = new MockCallback;
linkify(input, m.mockCallback.bind(m)); linkify(input, m.mockCallback.bind(m));
assert.deepEqual(output, m.result); assert.deepEqual(output, m.result);
} }
function testLink(assert, link, expectFail = false) { function testLink(assert, link, expectFail = false): void {
const input = link; const input = link;
const output = expectFail ? [{ type: "text", text: input }] : const output = expectFail ? [{ type: "text", text: input }] :
[{ type: "link", text: input }]; [{ type: "link", text: input }];
@ -67,23 +67,23 @@ export function tests() {
} }
return { return {
"Link with host": assert => { "Link with host": (assert): void => {
testLink(assert, "https://matrix.org"); testLink(assert, "https://matrix.org");
}, },
"Link with host & path": assert => { "Link with host & path": (assert): void => {
testLink(assert, "https://matrix.org/docs/develop"); testLink(assert, "https://matrix.org/docs/develop");
}, },
"Link with host & fragment": assert => { "Link with host & fragment": (assert): void => {
testLink(assert, "https://matrix.org#test"); testLink(assert, "https://matrix.org#test");
}, },
"Link with host & query": assert => { "Link with host & query": (assert): void => {
testLink(assert, "https://matrix.org/?foo=bar"); testLink(assert, "https://matrix.org/?foo=bar");
}, },
"Complex link": assert => { "Complex link": (assert): void => {
const link = "https://www.foobar.com/url?sa=t&rct=j&q=&esrc=s&source" + const link = "https://www.foobar.com/url?sa=t&rct=j&q=&esrc=s&source" +
"=web&cd=&cad=rja&uact=8&ved=2ahUKEwjyu7DJ-LHwAhUQyzgGHc" + "=web&cd=&cad=rja&uact=8&ved=2ahUKEwjyu7DJ-LHwAhUQyzgGHc" +
"OKA70QFjAAegQIBBAD&url=https%3A%2F%2Fmatrix.org%2Fdocs%" + "OKA70QFjAAegQIBBAD&url=https%3A%2F%2Fmatrix.org%2Fdocs%" +
@ -92,26 +92,26 @@ export function tests() {
testLink(assert, link); testLink(assert, link);
}, },
"Localhost link": assert => { "Localhost link": (assert): void => {
testLink(assert, "http://localhost"); testLink(assert, "http://localhost");
testLink(assert, "http://localhost:3000"); testLink(assert, "http://localhost:3000");
}, },
"IPV4 link": assert => { "IPV4 link": (assert): void => {
testLink(assert, "https://192.0.0.1"); testLink(assert, "https://192.0.0.1");
testLink(assert, "https://250.123.67.23:5924"); testLink(assert, "https://250.123.67.23:5924");
}, },
"IPV6 link": assert => { "IPV6 link": (assert): void => {
testLink(assert, "http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]"); testLink(assert, "http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]");
testLink(assert, "http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:7000"); testLink(assert, "http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:7000");
}, },
"Missing scheme must not linkify": assert => { "Missing scheme must not linkify": (assert): void => {
testLink(assert, "matrix.org/foo/bar", true); testLink(assert, "matrix.org/foo/bar", true);
}, },
"Punctuation at end of link must not linkify": assert => { "Punctuation at end of link must not linkify": (assert): void => {
const link = "https://foo.bar/?nenjil=lal810"; const link = "https://foo.bar/?nenjil=lal810";
const end = ".,? "; const end = ".,? ";
for (const char of end) { for (const char of end) {
@ -120,28 +120,28 @@ export function tests() {
} }
}, },
"Link doesn't adopt closing parenthesis": assert => { "Link doesn't adopt closing parenthesis": (assert): void => {
const link = "(https://matrix.org)"; const link = "(https://matrix.org)";
const out = [{ type: "text", text: "(" }, { type: "link", text: "https://matrix.org" }, { type: "text", text: ")" }]; const out = [{ type: "text", text: "(" }, { type: "link", text: "https://matrix.org" }, { type: "text", text: ")" }];
test(assert, link, out); test(assert, link, out);
}, },
"Unicode in hostname must not linkify": assert => { "Unicode in hostname must not linkify": (assert): void => {
const link = "https://foo.bar\uD83D\uDE03.com"; const link = "https://foo.bar\uD83D\uDE03.com";
const out = [{ type: "link", text: "https://foo.bar" }, const out = [{ type: "link", text: "https://foo.bar" },
{ type: "text", text: "\uD83D\uDE03.com" }]; { type: "text", text: "\uD83D\uDE03.com" }];
test(assert, link, out); test(assert, link, out);
}, },
"Link with unicode only after / must linkify": assert => { "Link with unicode only after / must linkify": (assert): void => {
testLink(assert, "https://foo.bar.com/\uD83D\uDE03"); testLink(assert, "https://foo.bar.com/\uD83D\uDE03");
}, },
"Link with unicode after fragment without path must linkify": assert => { "Link with unicode after fragment without path must linkify": (assert): void => {
testLink(assert, "https://foo.bar.com#\uD83D\uDE03"); testLink(assert, "https://foo.bar.com#\uD83D\uDE03");
}, },
"Link ends with <": assert => { "Link ends with <": (assert): void => {
const link = "https://matrix.org<"; const link = "https://matrix.org<";
const out = [{ type: "link", text: "https://matrix.org" }, { type: "text", text: "<" }]; const out = [{ type: "link", text: "https://matrix.org" }, { type: "text", text: "<" }];
test(assert, link, out); test(assert, link, out);