diff --git a/src/components/views/elements/Pill.js b/src/components/views/elements/Pill.js
index 3094f17fb7..daa4cb70e2 100644
--- a/src/components/views/elements/Pill.js
+++ b/src/components/views/elements/Pill.js
@@ -1,7 +1,7 @@
/*
Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd
-Copyright 2019 The Matrix.org Foundation C.I.C.
+Copyright 2019, 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -23,23 +23,11 @@ import { Room, RoomMember } from 'matrix-js-sdk';
import PropTypes from 'prop-types';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import FlairStore from "../../../stores/FlairStore";
-import {getPrimaryPermalinkEntity} from "../../../utils/permalinks/Permalinks";
+import {getPrimaryPermalinkEntity, parseAppLocalLink} from "../../../utils/permalinks/Permalinks";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {Action} from "../../../dispatcher/actions";
-// For URLs of matrix.to links in the timeline which have been reformatted by
-// HttpUtils transformTags to relative links. This excludes event URLs (with `[^\/]*`)
-const REGEX_LOCAL_PERMALINK = /^#\/(?:user|room|group)\/(([#!@+]).*?)(?=\/|\?|$)/;
-
class Pill extends React.Component {
- static isPillUrl(url) {
- return !!getPrimaryPermalinkEntity(url);
- }
-
- static isMessagePillUrl(url) {
- return !!REGEX_LOCAL_PERMALINK.exec(url);
- }
-
static roomNotifPos(text) {
return text.indexOf("@room");
}
@@ -56,7 +44,7 @@ class Pill extends React.Component {
static propTypes = {
// The Type of this Pill. If url is given, this is auto-detected.
type: PropTypes.string,
- // The URL to pillify (no validation is done, see isPillUrl and isMessagePillUrl)
+ // The URL to pillify (no validation is done)
url: PropTypes.string,
// Whether the pill is in a message
inMessage: PropTypes.bool,
@@ -90,12 +78,9 @@ class Pill extends React.Component {
if (nextProps.url) {
if (nextProps.inMessage) {
- // Default to the empty array if no match for simplicity
- // resource and prefix will be undefined instead of throwing
- const matrixToMatch = REGEX_LOCAL_PERMALINK.exec(nextProps.url) || [];
-
- resourceId = matrixToMatch[1]; // The room/user ID
- prefix = matrixToMatch[2]; // The first character of prefix
+ const parts = parseAppLocalLink(nextProps.url);
+ resourceId = parts.primaryEntityId; // The room/user ID
+ prefix = parts.sigil; // The first character of prefix
} else {
resourceId = getPrimaryPermalinkEntity(nextProps.url);
prefix = resourceId ? resourceId[0] : undefined;
diff --git a/src/utils/permalinks/ElementPermalinkConstructor.js b/src/utils/permalinks/ElementPermalinkConstructor.js
index 5ef0598ba1..29f2602304 100644
--- a/src/utils/permalinks/ElementPermalinkConstructor.js
+++ b/src/utils/permalinks/ElementPermalinkConstructor.js
@@ -1,5 +1,5 @@
/*
-Copyright 2019 The Matrix.org Foundation C.I.C.
+Copyright 2019, 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -76,6 +76,10 @@ export default class ElementPermalinkConstructor extends PermalinkConstructor {
}
const parts = fullUrl.substring(`${this._elementUrl}/#/`.length).split("/");
+ return ElementPermalinkConstructor.parseLinkParts(parts);
+ }
+
+ static parseLinkParts(parts: string[]): PermalinkParts {
if (parts.length < 2) { // we're expecting an entity and an ID of some kind at least
throw new Error("URL is missing parts");
}
diff --git a/src/utils/permalinks/PermalinkConstructor.js b/src/utils/permalinks/PermalinkConstructor.js
index f74c432bf0..25855eb024 100644
--- a/src/utils/permalinks/PermalinkConstructor.js
+++ b/src/utils/permalinks/PermalinkConstructor.js
@@ -1,5 +1,5 @@
/*
-Copyright 2019 The Matrix.org Foundation C.I.C.
+Copyright 2019, 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -80,4 +80,12 @@ export class PermalinkParts {
static forEvent(roomId: string, eventId: string, viaServers: string[]): PermalinkParts {
return new PermalinkParts(roomId, eventId, null, null, viaServers || []);
}
+
+ get primaryEntityId(): string {
+ return this.roomIdOrAlias || this.userId || this.groupId;
+ }
+
+ get sigil(): string {
+ return this.primaryEntityId[0];
+ }
}
diff --git a/src/utils/permalinks/Permalinks.js b/src/utils/permalinks/Permalinks.js
index e157ecc55e..9851bc3cb3 100644
--- a/src/utils/permalinks/Permalinks.js
+++ b/src/utils/permalinks/Permalinks.js
@@ -1,5 +1,5 @@
/*
-Copyright 2019 New Vector Ltd
+Copyright 2019, 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -405,6 +405,23 @@ export function parsePermalink(fullUrl: string): PermalinkParts {
return null; // not a permalink we can handle
}
+/**
+ * Parses an app local link (`#/(user|room|group)/identifer`) to a Matrix entity
+ * (room, user, group). Such links are produced by `HtmlUtils` when encountering
+ * links, which calls `tryTransformPermalinkToLocalHref` in this module.
+ * @param {string} localLink The app local link
+ * @returns {PermalinkParts}
+ */
+export function parseAppLocalLink(localLink: string): PermalinkParts {
+ try {
+ const segments = localLink.replace("#/", "").split("/");
+ return ElementPermalinkConstructor.parseLinkParts(segments);
+ } catch (e) {
+ // Ignore failures
+ }
+ return null;
+}
+
function getServerName(userId) {
return userId.split(":").splice(1).join(":");
}
diff --git a/src/utils/pillify.js b/src/utils/pillify.js
index 432771592a..72e0e5a4db 100644
--- a/src/utils/pillify.js
+++ b/src/utils/pillify.js
@@ -1,5 +1,5 @@
/*
-Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
+Copyright 2019, 2020, 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -19,7 +19,8 @@ import ReactDOM from 'react-dom';
import {MatrixClientPeg} from '../MatrixClientPeg';
import SettingsStore from "../settings/SettingsStore";
import {PushProcessor} from 'matrix-js-sdk/src/pushprocessor';
-import * as sdk from '../index';
+import Pill from "../components/views/elements/Pill";
+import { parseAppLocalLink } from "./permalinks/Permalinks";
/**
* Recurses depth-first through a DOM tree, converting matrix.to links
@@ -43,10 +44,10 @@ export function pillifyLinks(nodes, mxEvent, pills) {
if (node.tagName === "A" && node.getAttribute("href")) {
const href = node.getAttribute("href");
-
+ const parts = parseAppLocalLink(href);
// If the link is a (localised) matrix.to link, replace it with a pill
- const Pill = sdk.getComponent('elements.Pill');
- if (Pill.isMessagePillUrl(href)) {
+ // We don't want to pill event permalinks, so those are ignored.
+ if (parts && !parts.eventId) {
const pillContainer = document.createElement('span');
const pill = ", () => {
'style="width: 16px; height: 16px;" title="@member:domain.bla" alt="" aria-hidden="true">Member' +
'');
});
+
+ it("pills do not appear for event permalinks", () => {
+ const ev = mkEvent({
+ type: "m.room.message",
+ room: "room_id",
+ user: "sender",
+ content: {
+ body:
+ "An [event link](https://matrix.to/#/!ZxbRYPQXDXKGmDnJNg:example.com/" +
+ "$16085560162aNpaH:example.com?via=example.com) with text",
+ msgtype: "m.text",
+ format: "org.matrix.custom.html",
+ formatted_body:
+ "An event link with text",
+ },
+ event: true,
+ });
+
+ const wrapper = mount();
+ expect(wrapper.text()).toBe("An event link with text");
+ const content = wrapper.find(".mx_EventTile_body");
+ expect(content.html()).toBe(
+ '' +
+ 'An event link with text',
+ );
+ });
});
it("renders url previews correctly", () => {