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", () => {