Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Upgrade linkify to v3.0 (#7282)
Browse files Browse the repository at this point in the history
Co-authored-by: Timo K <toger5@hotmail.de>
  • Loading branch information
Palid and toger5 authored Jan 18, 2022
1 parent c068133 commit 336e1ae
Show file tree
Hide file tree
Showing 4 changed files with 312 additions and 225 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@
"is-ip": "^3.1.0",
"jszip": "^3.7.0",
"katex": "^0.12.0",
"linkifyjs": "^2.1.9",
"linkify-element": "^3.0.4",
"linkify-string": "^3.0.4",
"linkifyjs": "^3.0.5",
"lodash": "^4.17.20",
"maplibre-gl": "^1.15.2",
"matrix-analytics-events": "https://github.com/matrix-org/matrix-analytics-events.git#1eab4356548c97722a183912fda1ceabbe8cc7c1",
Expand Down
172 changes: 105 additions & 67 deletions src/linkify-matrix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ limitations under the License.
*/

import * as linkifyjs from 'linkifyjs';
import linkifyElement from 'linkifyjs/element';
import linkifyString from 'linkifyjs/string';
import linkifyElement from 'linkify-element';
import linkifyString from 'linkify-string';
import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
import { registerPlugin } from 'linkifyjs';

import { baseUrl } from "./utils/permalinks/SpecPermalinkConstructor";
import {
Expand All @@ -37,73 +38,82 @@ enum Type {
GroupId = "groupid"
}

// Linkifyjs types don't have parser, which really makes this harder.
const linkifyTokens = (linkifyjs as any).scanner.TOKENS;
enum MatrixLinkInitialToken {
POUND = linkifyTokens.POUND,
PLUS = linkifyTokens.PLUS,
AT = linkifyTokens.AT,
}
// Linkify stuff doesn't type scanner/parser/utils properly :/
function matrixOpaqueIdLinkifyParser({
scanner,
parser,
utils,
token,
name,
}: {
scanner: any;
parser: any;
utils: any;
token: '#' | '+' | '@';
name: Type;
}) {
const {
DOMAIN,
DOT,
// A generic catchall text token
TEXT,
NUM,
TLD,
COLON,
SYM,
UNDERSCORE,
// because 'localhost' is tokenised to the localhost token,
// usernames @localhost:foo.com are otherwise not matched!
LOCALHOST,
} = scanner.tokens;

/**
* Token should be one of the type of linkify.parser.TOKENS[AT | PLUS | POUND]
* but due to typing issues it's just not a feasible solution.
* This problem kind of gets solved in linkify 3.0
*/
function parseFreeformMatrixLinks(linkify, token: MatrixLinkInitialToken, type: Type): void {
// Text tokens
const TT = linkify.scanner.TOKENS;
// Multi tokens
const MT = linkify.parser.TOKENS;
const MultiToken = MT.Base;
const S_START = linkify.parser.start;

const TOKEN = function(value) {
MultiToken.call(this, value);
this.type = type;
this.isLink = true;
};
TOKEN.prototype = new MultiToken();

const S_TOKEN = S_START.jump(token);
const S_TOKEN_NAME = new linkify.parser.State();
const S_TOKEN_NAME_COLON = new linkify.parser.State();
const S_TOKEN_NAME_COLON_DOMAIN = new linkify.parser.State(TOKEN);
const S_TOKEN_NAME_COLON_DOMAIN_DOT = new linkify.parser.State();
const S_MX_LINK = new linkify.parser.State(TOKEN);
const S_MX_LINK_COLON = new linkify.parser.State();
const S_MX_LINK_COLON_NUM = new linkify.parser.State(TOKEN);

const allowedFreeformTokens = [
TT.DOT,
TT.PLUS,
TT.NUM,
TT.DOMAIN,
TT.TLD,
TT.UNDERSCORE,
token,
const S_START = parser.start;
const matrixSymbol = utils.createTokenClass(name, { isLink: true });

const localpartTokens = [
DOMAIN,
// IPV4 necessity
NUM,
TLD,

// because 'localhost' is tokenised to the localhost token,
// usernames @localhost:foo.com are otherwise not matched!
TT.LOCALHOST,
LOCALHOST,
SYM,
UNDERSCORE,
TEXT,
];
const domainpartTokens = [DOMAIN, NUM, TLD, LOCALHOST];

const INITIAL_STATE = S_START.tt(token);

S_TOKEN.on(allowedFreeformTokens, S_TOKEN_NAME);
S_TOKEN_NAME.on(allowedFreeformTokens, S_TOKEN_NAME);
S_TOKEN_NAME.on(TT.DOMAIN, S_TOKEN_NAME);
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);
}

S_TOKEN_NAME.on(TT.COLON, S_TOKEN_NAME_COLON);
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);
}

S_TOKEN_NAME_COLON.on(TT.DOMAIN, S_TOKEN_NAME_COLON_DOMAIN);
S_TOKEN_NAME_COLON.on(TT.LOCALHOST, S_MX_LINK); // accept #foo:localhost
S_TOKEN_NAME_COLON.on(TT.TLD, S_MX_LINK); // accept #foo:com (mostly for (TLD|DOMAIN)+ mixing)
S_TOKEN_NAME_COLON_DOMAIN.on(TT.DOT, S_TOKEN_NAME_COLON_DOMAIN_DOT);
S_TOKEN_NAME_COLON_DOMAIN_DOT.on(TT.DOMAIN, S_TOKEN_NAME_COLON_DOMAIN);
S_TOKEN_NAME_COLON_DOMAIN_DOT.on(TT.TLD, S_MX_LINK);
// 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);
}

S_MX_LINK.on(TT.DOT, S_TOKEN_NAME_COLON_DOMAIN_DOT); // accept repeated TLDs (e.g .org.uk)
S_MX_LINK.on(TT.COLON, S_MX_LINK_COLON); // do not accept trailing `:`
S_MX_LINK_COLON.on(TT.NUM, S_MX_LINK_COLON_NUM); // but do accept :NUM (port specifier)
const PORT_STATE = DOMAINPART_STATE.tt(COLON);

PORT_STATE.tt(NUM, matrixSymbol);
}

function onUserClick(event: MouseEvent, userId: string) {
Expand Down Expand Up @@ -199,10 +209,12 @@ export const options = {
}
},

linkAttributes: {
attributes: {
rel: 'noreferrer noopener',
},

className: 'linkified',

target: function(href: string, type: Type | string): string {
if (type === Type.URL) {
try {
Expand All @@ -221,12 +233,38 @@ export const options = {
};

// Run the plugins
// Linkify room aliases
parseFreeformMatrixLinks(linkifyjs, MatrixLinkInitialToken.POUND, Type.RoomAlias);
// Linkify group IDs
parseFreeformMatrixLinks(linkifyjs, MatrixLinkInitialToken.PLUS, Type.GroupId);
// Linkify user IDs
parseFreeformMatrixLinks(linkifyjs, MatrixLinkInitialToken.AT, Type.UserId);
registerPlugin(Type.RoomAlias, ({ scanner, parser, utils }) => {
const token = scanner.tokens.POUND as '#';
return matrixOpaqueIdLinkifyParser({
scanner,
parser,
utils,
token,
name: Type.RoomAlias,
});
});

registerPlugin(Type.GroupId, ({ scanner, parser, utils }) => {
const token = scanner.tokens.PLUS as '+';
return matrixOpaqueIdLinkifyParser({
scanner,
parser,
utils,
token,
name: Type.GroupId,
});
});

registerPlugin(Type.UserId, ({ scanner, parser, utils }) => {
const token = scanner.tokens.AT as '@';
return matrixOpaqueIdLinkifyParser({
scanner,
parser,
utils,
token,
name: Type.UserId,
});
});

export const linkify = linkifyjs;
export const _linkifyElement = linkifyElement;
Expand Down
Loading

0 comments on commit 336e1ae

Please sign in to comment.