Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Make pill @ and matrix.to links behave the same #7559

Closed
wants to merge 11 commits into from
11 changes: 7 additions & 4 deletions src/HtmlUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ import katex from 'katex';
import { AllHtmlEntities } from 'html-entities';
import { IContent } from 'matrix-js-sdk/src/models/event';

import { _linkifyElement, _linkifyString } from './linkify-matrix';
import { MATRIXTO_URL_PATTERN, _linkifyElement, _linkifyString } from './linkify-matrix';
import { IExtendedSanitizeOptions } from './@types/sanitize-html';
import SettingsStore from './settings/SettingsStore';
import { tryTransformPermalinkToLocalHref } from "./utils/permalinks/Permalinks";
import { tryTransformEntityToPermalink } from "./utils/permalinks/Permalinks";
import { getEmojiFromUnicode } from "./emoji";
import ReplyChain from "./components/views/elements/ReplyChain";
import { mediaFromMxc } from "./customisations/Media";
Expand Down Expand Up @@ -175,8 +175,11 @@ const transformTags: IExtendedSanitizeOptions["transformTags"] = { // custom to
if (attribs.href) {
attribs.target = '_blank'; // by default

const transformed = tryTransformPermalinkToLocalHref(attribs.href);
if (transformed !== attribs.href || attribs.href.match(ELEMENT_URL_PATTERN)) {
const transformed = tryTransformEntityToPermalink(attribs.href);
if (transformed !== attribs.href || // for matrix symbols e.g. @user:server.tdl
attribs.href.match(ELEMENT_URL_PATTERN) || // for https:vector|riot...
attribs.href.match(MATRIXTO_URL_PATTERN) // for matrix.to
) {
attribs.href = transformed;
delete attribs.target;
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/views/elements/Pill.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import FlairStore from "../../../stores/FlairStore";
import { getPrimaryPermalinkEntity, parseAppLocalLink } from "../../../utils/permalinks/Permalinks";
import { getPrimaryPermalinkEntity, parsePermalink } from "../../../utils/permalinks/Permalinks";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { Action } from "../../../dispatcher/actions";
import { mediaFromMxc } from "../../../customisations/Media";
Expand Down Expand Up @@ -85,7 +85,7 @@ class Pill extends React.Component {

if (nextProps.url) {
if (nextProps.inMessage) {
const parts = parseAppLocalLink(nextProps.url);
const parts = parsePermalink(nextProps.url);
resourceId = parts.primaryEntityId; // The room/user ID
prefix = parts.sigil; // The first character of prefix
} else {
Expand Down
11 changes: 8 additions & 3 deletions src/linkify-matrix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import { baseUrl } from "./utils/permalinks/SpecPermalinkConstructor";
import {
parsePermalink,
tryTransformEntityToPermalink,
tryTransformPermalinkToLocalHref,
} from "./utils/permalinks/Permalinks";
import dis from './dispatcher/dispatcher';
import { Action } from './dispatcher/actions';
Expand Down Expand Up @@ -117,20 +116,23 @@ function matrixOpaqueIdLinkifyParser({
}

function onUserClick(event: MouseEvent, userId: string) {
event.preventDefault();
const member = new RoomMember(null, userId);
if (!member) { return; }
dis.dispatch<ViewUserPayload>({
action: Action.ViewUser,
member: member,
});
}

function onAliasClick(event: MouseEvent, roomAlias: string) {
event.preventDefault();
dis.dispatch({
action: Action.ViewRoom,
room_alias: roomAlias,
});
}

function onGroupClick(event: MouseEvent, groupId: string) {
event.preventDefault();
dis.dispatch({ action: 'view_group', group_id: groupId });
Expand Down Expand Up @@ -218,8 +220,11 @@ export const options = {
target: function(href: string, type: Type | string): string {
if (type === Type.URL) {
try {
const transformed = tryTransformPermalinkToLocalHref(href);
if (transformed !== href || decodeURIComponent(href).match(ELEMENT_URL_PATTERN)) {
const transformed = tryTransformEntityToPermalink(href);
if (transformed !== href || // for matrix symbols e.g. @user:server.tdl
decodeURIComponent(href).match(ELEMENT_URL_PATTERN) || // for https:vector|riot...
decodeURIComponent(href).match(MATRIXTO_URL_PATTERN) // for matrix.to
) {
return null;
} else {
return '_blank';
Expand Down
26 changes: 4 additions & 22 deletions src/utils/permalinks/Permalinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,21 +318,20 @@ export function isPermalinkHost(host: string): boolean {

/**
* Transforms an entity (permalink, room alias, user ID, etc) into a local URL
* if possible. If the given entity is not found to be valid enough to be converted
* then a null value will be returned.
* if possible. If it is already a permalink (matrix.to) it gets returned
* unchanged.
* @param {string} entity The entity to transform.
* @returns {string|null} The transformed permalink or null if unable.
*/
export function tryTransformEntityToPermalink(entity: string): string {
if (!entity) return null;

// Check to see if it is a bare entity for starters
// Check if it is a bare entity for starters
if (entity[0] === '#' || entity[0] === '!') return makeRoomPermalink(entity);
if (entity[0] === '@') return makeUserPermalink(entity);
if (entity[0] === '+') return makeGroupPermalink(entity);

// Then try and merge it into a permalink
return tryTransformPermalinkToLocalHref(entity);
return entity;
}

/**
Expand Down Expand Up @@ -425,23 +424,6 @@ 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("#/", "");
return ElementPermalinkConstructor.parseAppRoute(segments);
} catch (e) {
// Ignore failures
}
return null;
}

function getServerName(userId: string): string {
return userId.split(":").splice(1).join(":");
}
Expand Down
4 changes: 2 additions & 2 deletions src/utils/pillify.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { MatrixClientPeg } from '../MatrixClientPeg';
import SettingsStore from "../settings/SettingsStore";
import Pill from "../components/views/elements/Pill";
import { parseAppLocalLink } from "./permalinks/Permalinks";
import { parsePermalink } from "./permalinks/Permalinks";

/**
* Recurses depth-first through a DOM tree, converting matrix.to links
Expand All @@ -46,7 +46,7 @@ export function pillifyLinks(nodes: ArrayLike<Element>, mxEvent: MatrixEvent, pi

if (node.tagName === "A" && node.getAttribute("href")) {
const href = node.getAttribute("href");
const parts = parseAppLocalLink(href);
const parts = parsePermalink(href);
// If the link is a (localised) matrix.to link, replace it with a pill
// We don't want to pill event permalinks, so those are ignored.
if (parts && !parts.eventId) {
Expand Down
5 changes: 3 additions & 2 deletions test/components/views/messages/TextualBody-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ describe("<TextualBody />", () => {
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/' +
'An <a href="https://matrix.to/#/!ZxbRYPQXDXKGmDnJNg:example.com/' +
'$16085560162aNpaH:example.com?via=example.com" ' +
'rel="noreferrer noopener">event link</a> with text</span>',
);
Expand Down Expand Up @@ -274,7 +274,8 @@ describe("<TextualBody />", () => {
const content = wrapper.find(".mx_EventTile_body");
expect(content.html()).toBe(
'<span class="mx_EventTile_body markdown-body" dir="auto">' +
'A <span><a class="mx_Pill mx_RoomPill" href="#/room/!ZxbRYPQXDXKGmDnJNg:example.com' +
'A <span><a class="mx_Pill mx_RoomPill" ' +
'href="https://matrix.to/#/!ZxbRYPQXDXKGmDnJNg:example.com' +
'?via=example.com&amp;via=bob.com"' +
'><img class="mx_BaseAvatar mx_BaseAvatar_image" ' +
'src="mxc://avatar.url/room.png" ' +
Expand Down