2015-07-10 13:37:13 +00:00
|
|
|
/*
|
2016-01-07 04:06:39 +00:00
|
|
|
Copyright 2015, 2016 OpenMarket Ltd
|
2019-10-01 02:17:54 +00:00
|
|
|
Copyright 2019 The Matrix.org Foundation C.I.C.
|
2015-07-10 13:37:13 +00:00
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2022-02-04 17:21:03 +00:00
|
|
|
import * as linkifyjs from 'linkifyjs';
|
|
|
|
import { registerCustomProtocol, registerPlugin } from 'linkifyjs';
|
|
|
|
import linkifyElement from 'linkify-element';
|
|
|
|
import linkifyString from 'linkify-string';
|
2021-12-03 14:00:56 +00:00
|
|
|
import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
|
2021-12-09 09:10:23 +00:00
|
|
|
|
2022-01-20 17:18:47 +00:00
|
|
|
import { baseUrl } from "./utils/permalinks/MatrixToPermalinkConstructor";
|
2020-03-20 00:42:44 +00:00
|
|
|
import {
|
|
|
|
parsePermalink,
|
|
|
|
tryTransformEntityToPermalink,
|
2020-03-20 00:56:41 +00:00
|
|
|
tryTransformPermalinkToLocalHref,
|
2020-03-20 00:42:44 +00:00
|
|
|
} from "./utils/permalinks/Permalinks";
|
2021-12-03 14:00:56 +00:00
|
|
|
import dis from './dispatcher/dispatcher';
|
|
|
|
import { Action } from './dispatcher/actions';
|
|
|
|
import { ViewUserPayload } from './dispatcher/payloads/ViewUserPayload';
|
2022-02-10 14:29:55 +00:00
|
|
|
import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
|
2022-03-22 23:07:37 +00:00
|
|
|
import { showGroupReplacedWithSpacesDialog } from "./group_helpers";
|
2017-12-10 12:50:41 +00:00
|
|
|
|
2022-01-20 17:18:47 +00:00
|
|
|
export enum Type {
|
2021-09-27 13:24:44 +00:00
|
|
|
URL = "url",
|
|
|
|
UserId = "userid",
|
|
|
|
RoomAlias = "roomalias",
|
2022-03-22 23:07:37 +00:00
|
|
|
GroupId = "groupid",
|
2021-09-27 13:24:44 +00:00
|
|
|
}
|
|
|
|
|
2022-01-18 17:24:16 +00:00
|
|
|
// 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 {
|
|
|
|
DOT,
|
2022-01-20 17:18:47 +00:00
|
|
|
// IPV4 necessity
|
2022-01-18 17:24:16 +00:00
|
|
|
NUM,
|
|
|
|
TLD,
|
|
|
|
COLON,
|
|
|
|
SYM,
|
2022-01-20 17:18:47 +00:00
|
|
|
HYPHEN,
|
2022-01-18 17:24:16 +00:00
|
|
|
UNDERSCORE,
|
|
|
|
// because 'localhost' is tokenised to the localhost token,
|
|
|
|
// usernames @localhost:foo.com are otherwise not matched!
|
|
|
|
LOCALHOST,
|
2022-01-20 17:18:47 +00:00
|
|
|
domain,
|
2022-01-18 17:24:16 +00:00
|
|
|
} = scanner.tokens;
|
2021-12-03 14:00:56 +00:00
|
|
|
|
2022-01-18 17:24:16 +00:00
|
|
|
const S_START = parser.start;
|
|
|
|
const matrixSymbol = utils.createTokenClass(name, { isLink: true });
|
|
|
|
|
2022-01-20 17:18:47 +00:00
|
|
|
const localpartTokens = [domain, TLD, LOCALHOST, SYM, UNDERSCORE, HYPHEN];
|
|
|
|
const domainpartTokens = [domain, TLD, LOCALHOST, HYPHEN];
|
2022-01-18 17:24:16 +00:00
|
|
|
|
|
|
|
const INITIAL_STATE = S_START.tt(token);
|
2015-07-10 13:37:13 +00:00
|
|
|
|
2022-01-20 17:18:47 +00:00
|
|
|
const LOCALPART_STATE = INITIAL_STATE.tt(domain);
|
2022-01-18 17:24:16 +00:00
|
|
|
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);
|
|
|
|
}
|
2017-06-05 15:51:50 +00:00
|
|
|
|
2022-01-18 17:24:16 +00:00
|
|
|
const DOMAINPART_STATE_DOT = LOCALPART_STATE.tt(COLON);
|
2022-01-20 17:18:47 +00:00
|
|
|
const DOMAINPART_STATE = DOMAINPART_STATE_DOT.tt(domain);
|
2022-01-18 17:24:16 +00:00
|
|
|
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);
|
|
|
|
}
|
2017-06-05 15:51:50 +00:00
|
|
|
|
2022-01-18 17:24:16 +00:00
|
|
|
// 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);
|
|
|
|
}
|
2017-06-05 15:51:50 +00:00
|
|
|
|
2022-01-18 17:24:16 +00:00
|
|
|
const PORT_STATE = DOMAINPART_STATE.tt(COLON);
|
|
|
|
|
|
|
|
PORT_STATE.tt(NUM, matrixSymbol);
|
2015-07-10 13:37:13 +00:00
|
|
|
}
|
|
|
|
|
2021-12-03 14:00:56 +00:00
|
|
|
function onUserClick(event: MouseEvent, userId: string) {
|
2022-01-20 17:18:47 +00:00
|
|
|
event.preventDefault();
|
2021-12-03 14:00:56 +00:00
|
|
|
const member = new RoomMember(null, userId);
|
|
|
|
if (!member) { return; }
|
|
|
|
dis.dispatch<ViewUserPayload>({
|
|
|
|
action: Action.ViewUser,
|
|
|
|
member: member,
|
|
|
|
});
|
|
|
|
}
|
2022-01-20 17:18:47 +00:00
|
|
|
|
2022-03-22 23:07:37 +00:00
|
|
|
function onGroupClick(event: MouseEvent, groupId: string) {
|
|
|
|
event.preventDefault();
|
|
|
|
showGroupReplacedWithSpacesDialog(groupId);
|
|
|
|
}
|
|
|
|
|
2021-12-03 14:00:56 +00:00
|
|
|
function onAliasClick(event: MouseEvent, roomAlias: string) {
|
|
|
|
event.preventDefault();
|
2022-02-10 14:29:55 +00:00
|
|
|
dis.dispatch<ViewRoomPayload>({
|
2022-01-12 20:12:28 +00:00
|
|
|
action: Action.ViewRoom,
|
|
|
|
room_alias: roomAlias,
|
2022-02-17 18:03:27 +00:00
|
|
|
metricsTrigger: "Timeline",
|
|
|
|
metricsViaKeyboard: false,
|
2022-01-12 20:12:28 +00:00
|
|
|
});
|
2021-12-03 14:00:56 +00:00
|
|
|
}
|
2022-01-20 17:18:47 +00:00
|
|
|
|
2021-09-27 13:24:44 +00:00
|
|
|
const escapeRegExp = function(string): string {
|
2016-03-20 12:31:30 +00:00
|
|
|
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
|
|
};
|
|
|
|
|
2020-12-21 12:46:29 +00:00
|
|
|
// Recognise URLs from both our local and official Element deployments.
|
2022-02-03 08:55:49 +00:00
|
|
|
// Anyone else really should be using matrix.to. vector:// allowed to support Element Desktop relative links.
|
2021-12-03 14:00:56 +00:00
|
|
|
export const ELEMENT_URL_PATTERN =
|
2022-02-03 08:55:49 +00:00
|
|
|
"^(?:vector://|https?://)?(?:" +
|
2020-12-21 12:46:29 +00:00
|
|
|
escapeRegExp(window.location.host + window.location.pathname) + "|" +
|
2020-12-21 13:15:00 +00:00
|
|
|
"(?:www\\.)?(?:riot|vector)\\.im/(?:app|beta|staging|develop)/|" +
|
|
|
|
"(?:app|beta|staging|develop)\\.element\\.io/" +
|
2020-12-21 12:46:29 +00:00
|
|
|
")(#.*)";
|
2016-08-28 00:55:42 +00:00
|
|
|
|
2021-12-03 14:00:56 +00:00
|
|
|
export const MATRIXTO_URL_PATTERN = "^(?:https?://)?(?:www\\.)?matrix\\.to/#/(([#@!+]).*)";
|
|
|
|
export const MATRIXTO_MD_LINK_PATTERN =
|
2019-09-18 08:27:43 +00:00
|
|
|
'\\[([^\\]]*)\\]\\((?:https?://)?(?:www\\.)?matrix\\.to/#/([#@!+][^\\)]*)\\)';
|
2021-12-03 14:00:56 +00:00
|
|
|
export const MATRIXTO_BASE_URL= baseUrl;
|
2016-03-20 03:05:07 +00:00
|
|
|
|
2021-12-03 14:00:56 +00:00
|
|
|
export const options = {
|
2021-09-27 13:24:44 +00:00
|
|
|
events: function(href: string, type: Type | string): Partial<GlobalEventHandlers> {
|
2015-07-10 13:37:13 +00:00
|
|
|
switch (type) {
|
2021-09-27 13:24:44 +00:00
|
|
|
case Type.URL: {
|
2020-03-20 00:42:44 +00:00
|
|
|
// intercept local permalinks to users and show them like userids (in userinfo of current room)
|
2020-03-26 10:45:26 +00:00
|
|
|
try {
|
|
|
|
const permalink = parsePermalink(href);
|
|
|
|
if (permalink && permalink.userId) {
|
|
|
|
return {
|
2021-09-28 05:53:52 +00:00
|
|
|
// @ts-ignore see https://linkify.js.org/docs/options.html
|
|
|
|
click: function(e) {
|
2021-12-03 14:00:56 +00:00
|
|
|
onUserClick(e, permalink.userId);
|
2020-03-26 10:45:26 +00:00
|
|
|
},
|
|
|
|
};
|
2022-01-20 17:18:47 +00:00
|
|
|
} else {
|
|
|
|
// for events, rooms etc. (anything other then users)
|
|
|
|
const localHref = tryTransformPermalinkToLocalHref(href);
|
|
|
|
if (localHref !== href) {
|
|
|
|
// it could be converted to a localHref -> therefore handle locally
|
|
|
|
return {
|
|
|
|
// @ts-ignore see https://linkify.js.org/docs/options.html
|
|
|
|
click: function(e) {
|
|
|
|
e.preventDefault();
|
|
|
|
window.location.hash = localHref;
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
2020-03-26 10:45:26 +00:00
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
// OK fine, it's not actually a permalink
|
2020-03-20 00:42:44 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2021-09-27 13:24:44 +00:00
|
|
|
case Type.UserId:
|
2015-10-27 09:58:55 +00:00
|
|
|
return {
|
2021-09-28 05:53:52 +00:00
|
|
|
// @ts-ignore see https://linkify.js.org/docs/options.html
|
|
|
|
click: function(e) {
|
2022-01-20 17:18:47 +00:00
|
|
|
const userId = parsePermalink(href).userId;
|
|
|
|
onUserClick(e, userId);
|
2017-10-11 16:56:17 +00:00
|
|
|
},
|
2015-10-27 09:58:55 +00:00
|
|
|
};
|
2021-09-27 13:24:44 +00:00
|
|
|
case Type.RoomAlias:
|
2015-10-27 09:58:55 +00:00
|
|
|
return {
|
2021-09-28 05:53:52 +00:00
|
|
|
// @ts-ignore see https://linkify.js.org/docs/options.html
|
|
|
|
click: function(e) {
|
2022-01-20 17:18:47 +00:00
|
|
|
const alias = parsePermalink(href).roomIdOrAlias;
|
|
|
|
onAliasClick(e, alias);
|
2017-10-11 16:56:17 +00:00
|
|
|
},
|
2015-10-27 09:58:55 +00:00
|
|
|
};
|
2022-03-22 23:07:37 +00:00
|
|
|
|
2021-09-27 13:24:44 +00:00
|
|
|
case Type.GroupId:
|
2017-06-05 15:51:50 +00:00
|
|
|
return {
|
2021-09-28 05:53:52 +00:00
|
|
|
// @ts-ignore see https://linkify.js.org/docs/options.html
|
|
|
|
click: function(e) {
|
2022-01-20 17:18:47 +00:00
|
|
|
const groupId = parsePermalink(href).groupId;
|
|
|
|
onGroupClick(e, groupId);
|
2017-10-11 16:56:17 +00:00
|
|
|
},
|
2017-06-05 15:51:50 +00:00
|
|
|
};
|
2015-07-10 13:37:13 +00:00
|
|
|
}
|
2016-02-18 18:16:39 +00:00
|
|
|
},
|
2016-08-28 01:05:23 +00:00
|
|
|
|
2021-09-27 13:24:44 +00:00
|
|
|
formatHref: function(href: string, type: Type | string): string {
|
2016-02-18 18:16:39 +00:00
|
|
|
switch (type) {
|
2021-09-27 13:24:44 +00:00
|
|
|
case Type.RoomAlias:
|
|
|
|
case Type.UserId:
|
|
|
|
case Type.GroupId:
|
2018-06-16 07:27:17 +00:00
|
|
|
default: {
|
2020-03-04 20:56:58 +00:00
|
|
|
return tryTransformEntityToPermalink(href);
|
2018-06-16 07:27:17 +00:00
|
|
|
}
|
2016-03-20 03:05:07 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2022-01-18 17:24:16 +00:00
|
|
|
attributes: {
|
2020-02-23 22:14:29 +00:00
|
|
|
rel: 'noreferrer noopener',
|
2016-08-15 20:37:26 +00:00
|
|
|
},
|
|
|
|
|
2022-01-18 17:24:16 +00:00
|
|
|
className: 'linkified',
|
|
|
|
|
2021-09-27 13:24:44 +00:00
|
|
|
target: function(href: string, type: Type | string): string {
|
|
|
|
if (type === Type.URL) {
|
2021-05-13 09:20:27 +00:00
|
|
|
try {
|
|
|
|
const transformed = tryTransformPermalinkToLocalHref(href);
|
2022-01-25 14:31:00 +00:00
|
|
|
if (
|
|
|
|
transformed !== href || // if it could be converted to handle locally for matrix symbols e.g. @user:server.tdl and matrix.to
|
|
|
|
decodeURIComponent(href).match(ELEMENT_URL_PATTERN) // for https links to Element domains
|
2022-01-20 17:18:47 +00:00
|
|
|
) {
|
2021-05-13 09:20:27 +00:00
|
|
|
return null;
|
|
|
|
} else {
|
|
|
|
return '_blank';
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
// malformed URI
|
2016-03-20 03:05:07 +00:00
|
|
|
}
|
|
|
|
}
|
2016-03-21 15:45:04 +00:00
|
|
|
return null;
|
2016-03-20 03:05:07 +00:00
|
|
|
},
|
2015-10-26 17:59:49 +00:00
|
|
|
};
|
2015-07-10 13:37:13 +00:00
|
|
|
|
2021-12-03 14:00:56 +00:00
|
|
|
// Run the plugins
|
2022-01-18 17:24:16 +00:00
|
|
|
registerPlugin(Type.RoomAlias, ({ scanner, parser, utils }) => {
|
|
|
|
const token = scanner.tokens.POUND as '#';
|
2022-01-25 17:37:54 +00:00
|
|
|
matrixOpaqueIdLinkifyParser({
|
2022-01-18 17:24:16 +00:00
|
|
|
scanner,
|
|
|
|
parser,
|
|
|
|
utils,
|
|
|
|
token,
|
|
|
|
name: Type.RoomAlias,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
registerPlugin(Type.GroupId, ({ scanner, parser, utils }) => {
|
|
|
|
const token = scanner.tokens.PLUS as '+';
|
2022-01-25 17:37:54 +00:00
|
|
|
matrixOpaqueIdLinkifyParser({
|
2022-01-18 17:24:16 +00:00
|
|
|
scanner,
|
|
|
|
parser,
|
|
|
|
utils,
|
|
|
|
token,
|
|
|
|
name: Type.GroupId,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
registerPlugin(Type.UserId, ({ scanner, parser, utils }) => {
|
|
|
|
const token = scanner.tokens.AT as '@';
|
2022-01-25 17:37:54 +00:00
|
|
|
matrixOpaqueIdLinkifyParser({
|
2022-01-18 17:24:16 +00:00
|
|
|
scanner,
|
|
|
|
parser,
|
|
|
|
utils,
|
|
|
|
token,
|
|
|
|
name: Type.UserId,
|
|
|
|
});
|
|
|
|
});
|
2021-12-03 14:00:56 +00:00
|
|
|
|
2022-01-20 17:18:47 +00:00
|
|
|
registerCustomProtocol("matrix", true);
|
|
|
|
|
2021-12-03 14:00:56 +00:00
|
|
|
export const linkify = linkifyjs;
|
|
|
|
export const _linkifyElement = linkifyElement;
|
|
|
|
export const _linkifyString = linkifyString;
|