Merge pull request #5575 from matrix-org/jryans/no-event-pills

Remove pills from event permalinks with text
pull/21833/head
J. Ryan Stinnett 2021-02-01 10:53:22 +00:00 committed by GitHub
commit 89b835dd20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 73 additions and 31 deletions

View File

@ -1,7 +1,7 @@
/* /*
Copyright 2017 Vector Creations Ltd Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 PropTypes from 'prop-types';
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {MatrixClientPeg} from '../../../MatrixClientPeg';
import FlairStore from "../../../stores/FlairStore"; import FlairStore from "../../../stores/FlairStore";
import {getPrimaryPermalinkEntity} from "../../../utils/permalinks/Permalinks"; import {getPrimaryPermalinkEntity, parseAppLocalLink} from "../../../utils/permalinks/Permalinks";
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {Action} from "../../../dispatcher/actions"; 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 { class Pill extends React.Component {
static isPillUrl(url) {
return !!getPrimaryPermalinkEntity(url);
}
static isMessagePillUrl(url) {
return !!REGEX_LOCAL_PERMALINK.exec(url);
}
static roomNotifPos(text) { static roomNotifPos(text) {
return text.indexOf("@room"); return text.indexOf("@room");
} }
@ -56,7 +44,7 @@ class Pill extends React.Component {
static propTypes = { static propTypes = {
// The Type of this Pill. If url is given, this is auto-detected. // The Type of this Pill. If url is given, this is auto-detected.
type: PropTypes.string, 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, url: PropTypes.string,
// Whether the pill is in a message // Whether the pill is in a message
inMessage: PropTypes.bool, inMessage: PropTypes.bool,
@ -90,12 +78,9 @@ class Pill extends React.Component {
if (nextProps.url) { if (nextProps.url) {
if (nextProps.inMessage) { if (nextProps.inMessage) {
// Default to the empty array if no match for simplicity const parts = parseAppLocalLink(nextProps.url);
// resource and prefix will be undefined instead of throwing resourceId = parts.primaryEntityId; // The room/user ID
const matrixToMatch = REGEX_LOCAL_PERMALINK.exec(nextProps.url) || []; prefix = parts.sigil; // The first character of prefix
resourceId = matrixToMatch[1]; // The room/user ID
prefix = matrixToMatch[2]; // The first character of prefix
} else { } else {
resourceId = getPrimaryPermalinkEntity(nextProps.url); resourceId = getPrimaryPermalinkEntity(nextProps.url);
prefix = resourceId ? resourceId[0] : undefined; prefix = resourceId ? resourceId[0] : undefined;

View File

@ -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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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("/"); 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 if (parts.length < 2) { // we're expecting an entity and an ID of some kind at least
throw new Error("URL is missing parts"); throw new Error("URL is missing parts");
} }

View File

@ -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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 { static forEvent(roomId: string, eventId: string, viaServers: string[]): PermalinkParts {
return new PermalinkParts(roomId, eventId, null, null, viaServers || []); 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];
}
} }

View File

@ -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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 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) { function getServerName(userId) {
return userId.split(":").splice(1).join(":"); return userId.split(":").splice(1).join(":");
} }

View File

@ -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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 {MatrixClientPeg} from '../MatrixClientPeg';
import SettingsStore from "../settings/SettingsStore"; import SettingsStore from "../settings/SettingsStore";
import {PushProcessor} from 'matrix-js-sdk/src/pushprocessor'; 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 * 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")) { if (node.tagName === "A" && node.getAttribute("href")) {
const href = 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 // If the link is a (localised) matrix.to link, replace it with a pill
const Pill = sdk.getComponent('elements.Pill'); // We don't want to pill event permalinks, so those are ignored.
if (Pill.isMessagePillUrl(href)) { if (parts && !parts.eventId) {
const pillContainer = document.createElement('span'); const pillContainer = document.createElement('span');
const pill = <Pill const pill = <Pill
@ -72,8 +73,6 @@ export function pillifyLinks(nodes, mxEvent, pills) {
// to clear the pills from the last run of pillifyLinks // to clear the pills from the last run of pillifyLinks
!node.parentElement.classList.contains("mx_AtRoomPill") !node.parentElement.classList.contains("mx_AtRoomPill")
) { ) {
const Pill = sdk.getComponent('elements.Pill');
let currentTextNode = node; let currentTextNode = node;
const roomNotifTextNodes = []; const roomNotifTextNodes = [];

View File

@ -213,6 +213,35 @@ describe("<TextualBody />", () => {
'style="width: 16px; height: 16px;" title="@member:domain.bla" alt="" aria-hidden="true">Member</a>' + 'style="width: 16px; height: 16px;" title="@member:domain.bla" alt="" aria-hidden="true">Member</a>' +
'</span></span>'); '</span></span>');
}); });
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 <a href=\"https://matrix.to/#/!ZxbRYPQXDXKGmDnJNg:example.com/" +
"$16085560162aNpaH:example.com?via=example.com\">event link</a> with text",
},
event: true,
});
const wrapper = mount(<TextualBody mxEvent={ev} />);
expect(wrapper.text()).toBe("An event link with text");
const content = wrapper.find(".mx_EventTile_body");
expect(content.html()).toBe(
'<span class="mx_EventTile_body markdown-body" dir="auto">' +
'An <a href="#/room/!ZxbRYPQXDXKGmDnJNg:example.com/' +
'$16085560162aNpaH:example.com?via=example.com" ' +
'rel="noreferrer noopener">event link</a> with text</span>',
);
});
}); });
it("renders url previews correctly", () => { it("renders url previews correctly", () => {