@@ -16,9 +16,10 @@ limitations under the License.
16
16
*/
17
17
18
18
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' ;
21
21
import { RoomMember } from 'matrix-js-sdk/src/models/room-member' ;
22
+ import { registerPlugin } from 'linkifyjs' ;
22
23
23
24
import { baseUrl } from "./utils/permalinks/SpecPermalinkConstructor" ;
24
25
import {
@@ -37,73 +38,82 @@ enum Type {
37
38
GroupId = "groupid"
38
39
}
39
40
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 ;
47
69
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 ,
85
78
86
79
// because 'localhost' is tokenised to the localhost token,
87
80
// usernames @localhost :foo.com are otherwise not matched!
88
- TT . LOCALHOST ,
81
+ LOCALHOST ,
82
+ SYM ,
83
+ UNDERSCORE ,
84
+ TEXT ,
89
85
] ;
86
+ const domainpartTokens = [ DOMAIN , NUM , TLD , LOCALHOST ] ;
87
+
88
+ const INITIAL_STATE = S_START . tt ( token ) ;
90
89
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
+ }
94
99
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
+ }
96
108
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
+ }
103
113
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 ) ;
107
117
}
108
118
109
119
function onUserClick ( event : MouseEvent , userId : string ) {
@@ -199,10 +209,12 @@ export const options = {
199
209
}
200
210
} ,
201
211
202
- linkAttributes : {
212
+ attributes : {
203
213
rel : 'noreferrer noopener' ,
204
214
} ,
205
215
216
+ className : 'linkified' ,
217
+
206
218
target : function ( href : string , type : Type | string ) : string {
207
219
if ( type === Type . URL ) {
208
220
try {
@@ -221,12 +233,38 @@ export const options = {
221
233
} ;
222
234
223
235
// 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
+ } ) ;
230
268
231
269
export const linkify = linkifyjs ;
232
270
export const _linkifyElement = linkifyElement ;
0 commit comments