Upgrade linkify to v3.0 (#7282)
Co-authored-by: Timo K <toger5@hotmail.de>
This commit is contained in:
parent
c0681333bf
commit
336e1ae3b6
4 changed files with 311 additions and 224 deletions
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue