Upgrade linkify to v3.0 (#7282)

Co-authored-by: Timo K <toger5@hotmail.de>
This commit is contained in:
Dariusz Niemczyk 2022-01-18 18:24:16 +01:00 committed by GitHub
parent c0681333bf
commit 336e1ae3b6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 311 additions and 224 deletions

View file

@ -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 {
@ -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 S_START = parser.start;
const matrixSymbol = utils.createTokenClass(name, { isLink: true });
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 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];
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 INITIAL_STATE = S_START.tt(token);
S_TOKEN_NAME.on(TT.COLON, S_TOKEN_NAME_COLON);
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_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);
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_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)
// 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);
}
function onUserClick(event: MouseEvent, userId: string) {
@ -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 {
@ -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;