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

Commit 336e1ae

Browse files
Palidtoger5
andauthored
Upgrade linkify to v3.0 (#7282)
Co-authored-by: Timo K <[email protected]>
1 parent c068133 commit 336e1ae

File tree

4 files changed

+312
-225
lines changed

4 files changed

+312
-225
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,9 @@
8585
"is-ip": "^3.1.0",
8686
"jszip": "^3.7.0",
8787
"katex": "^0.12.0",
88-
"linkifyjs": "^2.1.9",
88+
"linkify-element": "^3.0.4",
89+
"linkify-string": "^3.0.4",
90+
"linkifyjs": "^3.0.5",
8991
"lodash": "^4.17.20",
9092
"maplibre-gl": "^1.15.2",
9193
"matrix-analytics-events": "https://github.com/matrix-org/matrix-analytics-events.git#1eab4356548c97722a183912fda1ceabbe8cc7c1",

src/linkify-matrix.ts

Lines changed: 105 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ limitations under the License.
1616
*/
1717

1818
import * as linkifyjs from 'linkifyjs';
19-
import linkifyElement from 'linkifyjs/element';
20-
import linkifyString from 'linkifyjs/string';
19+
import linkifyElement from 'linkify-element';
20+
import linkifyString from 'linkify-string';
2121
import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
22+
import { registerPlugin } from 'linkifyjs';
2223

2324
import { baseUrl } from "./utils/permalinks/SpecPermalinkConstructor";
2425
import {
@@ -37,73 +38,82 @@ enum Type {
3738
GroupId = "groupid"
3839
}
3940

40-
// Linkifyjs types don't have parser, which really makes this harder.
41-
const linkifyTokens = (linkifyjs as any).scanner.TOKENS;
42-
enum MatrixLinkInitialToken {
43-
POUND = linkifyTokens.POUND,
44-
PLUS = linkifyTokens.PLUS,
45-
AT = linkifyTokens.AT,
46-
}
41+
// Linkify stuff doesn't type scanner/parser/utils properly :/
42+
function matrixOpaqueIdLinkifyParser({
43+
scanner,
44+
parser,
45+
utils,
46+
token,
47+
name,
48+
}: {
49+
scanner: any;
50+
parser: any;
51+
utils: any;
52+
token: '#' | '+' | '@';
53+
name: Type;
54+
}) {
55+
const {
56+
DOMAIN,
57+
DOT,
58+
// A generic catchall text token
59+
TEXT,
60+
NUM,
61+
TLD,
62+
COLON,
63+
SYM,
64+
UNDERSCORE,
65+
// because 'localhost' is tokenised to the localhost token,
66+
// usernames @localhost:foo.com are otherwise not matched!
67+
LOCALHOST,
68+
} = scanner.tokens;
4769

48-
/**
49-
* Token should be one of the type of linkify.parser.TOKENS[AT | PLUS | POUND]
50-
* but due to typing issues it's just not a feasible solution.
51-
* This problem kind of gets solved in linkify 3.0
52-
*/
53-
function parseFreeformMatrixLinks(linkify, token: MatrixLinkInitialToken, type: Type): void {
54-
// Text tokens
55-
const TT = linkify.scanner.TOKENS;
56-
// Multi tokens
57-
const MT = linkify.parser.TOKENS;
58-
const MultiToken = MT.Base;
59-
const S_START = linkify.parser.start;
60-
61-
const TOKEN = function(value) {
62-
MultiToken.call(this, value);
63-
this.type = type;
64-
this.isLink = true;
65-
};
66-
TOKEN.prototype = new MultiToken();
67-
68-
const S_TOKEN = S_START.jump(token);
69-
const S_TOKEN_NAME = new linkify.parser.State();
70-
const S_TOKEN_NAME_COLON = new linkify.parser.State();
71-
const S_TOKEN_NAME_COLON_DOMAIN = new linkify.parser.State(TOKEN);
72-
const S_TOKEN_NAME_COLON_DOMAIN_DOT = new linkify.parser.State();
73-
const S_MX_LINK = new linkify.parser.State(TOKEN);
74-
const S_MX_LINK_COLON = new linkify.parser.State();
75-
const S_MX_LINK_COLON_NUM = new linkify.parser.State(TOKEN);
76-
77-
const allowedFreeformTokens = [
78-
TT.DOT,
79-
TT.PLUS,
80-
TT.NUM,
81-
TT.DOMAIN,
82-
TT.TLD,
83-
TT.UNDERSCORE,
84-
token,
70+
const S_START = parser.start;
71+
const matrixSymbol = utils.createTokenClass(name, { isLink: true });
72+
73+
const localpartTokens = [
74+
DOMAIN,
75+
// IPV4 necessity
76+
NUM,
77+
TLD,
8578

8679
// because 'localhost' is tokenised to the localhost token,
8780
// usernames @localhost:foo.com are otherwise not matched!
88-
TT.LOCALHOST,
81+
LOCALHOST,
82+
SYM,
83+
UNDERSCORE,
84+
TEXT,
8985
];
86+
const domainpartTokens = [DOMAIN, NUM, TLD, LOCALHOST];
87+
88+
const INITIAL_STATE = S_START.tt(token);
9089

91-
S_TOKEN.on(allowedFreeformTokens, S_TOKEN_NAME);
92-
S_TOKEN_NAME.on(allowedFreeformTokens, S_TOKEN_NAME);
93-
S_TOKEN_NAME.on(TT.DOMAIN, S_TOKEN_NAME);
90+
const LOCALPART_STATE = INITIAL_STATE.tt(DOMAIN);
91+
for (const token of localpartTokens) {
92+
INITIAL_STATE.tt(token, LOCALPART_STATE);
93+
LOCALPART_STATE.tt(token, LOCALPART_STATE);
94+
}
95+
const LOCALPART_STATE_DOT = LOCALPART_STATE.tt(DOT);
96+
for (const token of localpartTokens) {
97+
LOCALPART_STATE_DOT.tt(token, LOCALPART_STATE);
98+
}
9499

95-
S_TOKEN_NAME.on(TT.COLON, S_TOKEN_NAME_COLON);
100+
const DOMAINPART_STATE_DOT = LOCALPART_STATE.tt(COLON);
101+
const DOMAINPART_STATE = DOMAINPART_STATE_DOT.tt(DOMAIN);
102+
DOMAINPART_STATE.tt(DOT, DOMAINPART_STATE_DOT);
103+
for (const token of domainpartTokens) {
104+
DOMAINPART_STATE.tt(token, DOMAINPART_STATE);
105+
// we are done if we have a domain
106+
DOMAINPART_STATE.tt(token, matrixSymbol);
107+
}
96108

97-
S_TOKEN_NAME_COLON.on(TT.DOMAIN, S_TOKEN_NAME_COLON_DOMAIN);
98-
S_TOKEN_NAME_COLON.on(TT.LOCALHOST, S_MX_LINK); // accept #foo:localhost
99-
S_TOKEN_NAME_COLON.on(TT.TLD, S_MX_LINK); // accept #foo:com (mostly for (TLD|DOMAIN)+ mixing)
100-
S_TOKEN_NAME_COLON_DOMAIN.on(TT.DOT, S_TOKEN_NAME_COLON_DOMAIN_DOT);
101-
S_TOKEN_NAME_COLON_DOMAIN_DOT.on(TT.DOMAIN, S_TOKEN_NAME_COLON_DOMAIN);
102-
S_TOKEN_NAME_COLON_DOMAIN_DOT.on(TT.TLD, S_MX_LINK);
109+
// accept repeated TLDs (e.g .org.uk) but do not accept double dots: ..
110+
for (const token of domainpartTokens) {
111+
DOMAINPART_STATE_DOT.tt(token, DOMAINPART_STATE);
112+
}
103113

104-
S_MX_LINK.on(TT.DOT, S_TOKEN_NAME_COLON_DOMAIN_DOT); // accept repeated TLDs (e.g .org.uk)
105-
S_MX_LINK.on(TT.COLON, S_MX_LINK_COLON); // do not accept trailing `:`
106-
S_MX_LINK_COLON.on(TT.NUM, S_MX_LINK_COLON_NUM); // but do accept :NUM (port specifier)
114+
const PORT_STATE = DOMAINPART_STATE.tt(COLON);
115+
116+
PORT_STATE.tt(NUM, matrixSymbol);
107117
}
108118

109119
function onUserClick(event: MouseEvent, userId: string) {
@@ -199,10 +209,12 @@ export const options = {
199209
}
200210
},
201211

202-
linkAttributes: {
212+
attributes: {
203213
rel: 'noreferrer noopener',
204214
},
205215

216+
className: 'linkified',
217+
206218
target: function(href: string, type: Type | string): string {
207219
if (type === Type.URL) {
208220
try {
@@ -221,12 +233,38 @@ export const options = {
221233
};
222234

223235
// Run the plugins
224-
// Linkify room aliases
225-
parseFreeformMatrixLinks(linkifyjs, MatrixLinkInitialToken.POUND, Type.RoomAlias);
226-
// Linkify group IDs
227-
parseFreeformMatrixLinks(linkifyjs, MatrixLinkInitialToken.PLUS, Type.GroupId);
228-
// Linkify user IDs
229-
parseFreeformMatrixLinks(linkifyjs, MatrixLinkInitialToken.AT, Type.UserId);
236+
registerPlugin(Type.RoomAlias, ({ scanner, parser, utils }) => {
237+
const token = scanner.tokens.POUND as '#';
238+
return matrixOpaqueIdLinkifyParser({
239+
scanner,
240+
parser,
241+
utils,
242+
token,
243+
name: Type.RoomAlias,
244+
});
245+
});
246+
247+
registerPlugin(Type.GroupId, ({ scanner, parser, utils }) => {
248+
const token = scanner.tokens.PLUS as '+';
249+
return matrixOpaqueIdLinkifyParser({
250+
scanner,
251+
parser,
252+
utils,
253+
token,
254+
name: Type.GroupId,
255+
});
256+
});
257+
258+
registerPlugin(Type.UserId, ({ scanner, parser, utils }) => {
259+
const token = scanner.tokens.AT as '@';
260+
return matrixOpaqueIdLinkifyParser({
261+
scanner,
262+
parser,
263+
utils,
264+
token,
265+
name: Type.UserId,
266+
});
267+
});
230268

231269
export const linkify = linkifyjs;
232270
export const _linkifyElement = linkifyElement;

0 commit comments

Comments
 (0)