mirror of
https://github.com/element-hq/element-web
synced 2024-11-26 11:15:53 +03:00
Directly convert Matrix and room Ids to pills (#10267)
This commit is contained in:
parent
de6a1a661c
commit
ac3c95fa84
2 changed files with 52 additions and 55 deletions
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { createRef, SyntheticEvent, MouseEvent, ReactNode } from "react";
|
import React, { createRef, SyntheticEvent, MouseEvent } from "react";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import highlight from "highlight.js";
|
import highlight from "highlight.js";
|
||||||
import { MsgType } from "matrix-js-sdk/src/@types/event";
|
import { MsgType } from "matrix-js-sdk/src/@types/event";
|
||||||
|
@ -86,21 +86,21 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private applyFormatting(): void {
|
private applyFormatting(): void {
|
||||||
const showLineNumbers = SettingsStore.getValue("showCodeLineNumbers");
|
// Function is only called from render / componentDidMount → contentRef is set
|
||||||
this.activateSpoilers([this.contentRef.current]);
|
const content = this.contentRef.current!;
|
||||||
|
|
||||||
// pillifyLinks BEFORE linkifyElement because plain room/user URLs in the composer
|
const showLineNumbers = SettingsStore.getValue("showCodeLineNumbers");
|
||||||
// are still sent as plaintext URLs. If these are ever pillified in the composer,
|
this.activateSpoilers([content]);
|
||||||
// we should be pillify them here by doing the linkifying BEFORE the pillifying.
|
|
||||||
pillifyLinks([this.contentRef.current], this.props.mxEvent, this.pills);
|
HtmlUtils.linkifyElement(content);
|
||||||
HtmlUtils.linkifyElement(this.contentRef.current);
|
pillifyLinks([content], this.props.mxEvent, this.pills);
|
||||||
|
|
||||||
this.calculateUrlPreview();
|
this.calculateUrlPreview();
|
||||||
|
|
||||||
// tooltipifyLinks AFTER calculateUrlPreview because the DOM inside the tooltip
|
// tooltipifyLinks AFTER calculateUrlPreview because the DOM inside the tooltip
|
||||||
// container is empty before the internal component has mounted so calculateUrlPreview
|
// container is empty before the internal component has mounted so calculateUrlPreview
|
||||||
// won't find any anchors
|
// won't find any anchors
|
||||||
tooltipifyLinks([this.contentRef.current], this.pills, this.tooltips);
|
tooltipifyLinks([content], this.pills, this.tooltips);
|
||||||
|
|
||||||
if (this.props.mxEvent.getContent().format === "org.matrix.custom.html") {
|
if (this.props.mxEvent.getContent().format === "org.matrix.custom.html") {
|
||||||
// Handle expansion and add buttons
|
// Handle expansion and add buttons
|
||||||
|
@ -578,18 +578,16 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
|
||||||
|
|
||||||
// only strip reply if this is the original replying event, edits thereafter do not have the fallback
|
// only strip reply if this is the original replying event, edits thereafter do not have the fallback
|
||||||
const stripReply = !mxEvent.replacingEvent() && !!getParentEventId(mxEvent);
|
const stripReply = !mxEvent.replacingEvent() && !!getParentEventId(mxEvent);
|
||||||
let body: ReactNode;
|
|
||||||
if (!body) {
|
|
||||||
isEmote = content.msgtype === MsgType.Emote;
|
isEmote = content.msgtype === MsgType.Emote;
|
||||||
isNotice = content.msgtype === MsgType.Notice;
|
isNotice = content.msgtype === MsgType.Notice;
|
||||||
body = HtmlUtils.bodyToHtml(content, this.props.highlights, {
|
let body = HtmlUtils.bodyToHtml(content, this.props.highlights, {
|
||||||
disableBigEmoji: isEmote || !SettingsStore.getValue<boolean>("TextualBody.enableBigEmoji"),
|
disableBigEmoji: isEmote || !SettingsStore.getValue<boolean>("TextualBody.enableBigEmoji"),
|
||||||
// Part of Replies fallback support
|
// Part of Replies fallback support
|
||||||
stripReplyFallback: stripReply,
|
stripReplyFallback: stripReply,
|
||||||
ref: this.contentRef,
|
ref: this.contentRef,
|
||||||
returnString: false,
|
returnString: false,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
if (this.props.replacingEventId) {
|
if (this.props.replacingEventId) {
|
||||||
body = (
|
body = (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||||
import { MockedObject } from "jest-mock";
|
import { MockedObject } from "jest-mock";
|
||||||
import { render } from "@testing-library/react";
|
import { render } from "@testing-library/react";
|
||||||
|
|
||||||
import { getMockClientWithEventEmitter, mkEvent, mkStubRoom } from "../../../test-utils";
|
import { getMockClientWithEventEmitter, mkEvent, mkMessage, mkStubRoom } from "../../../test-utils";
|
||||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||||
import * as languageHandler from "../../../../src/languageHandler";
|
import * as languageHandler from "../../../../src/languageHandler";
|
||||||
import DMRoomMap from "../../../../src/utils/DMRoomMap";
|
import DMRoomMap from "../../../../src/utils/DMRoomMap";
|
||||||
|
@ -28,6 +28,15 @@ import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
||||||
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
|
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
|
||||||
import { MediaEventHelper } from "../../../../src/utils/MediaEventHelper";
|
import { MediaEventHelper } from "../../../../src/utils/MediaEventHelper";
|
||||||
|
|
||||||
|
const mkRoomTextMessage = (body: string): MatrixEvent => {
|
||||||
|
return mkMessage({
|
||||||
|
msg: body,
|
||||||
|
room: "room_id",
|
||||||
|
user: "sender",
|
||||||
|
event: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
describe("<TextualBody />", () => {
|
describe("<TextualBody />", () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
jest.spyOn(MatrixClientPeg, "get").mockRestore();
|
jest.spyOn(MatrixClientPeg, "get").mockRestore();
|
||||||
|
@ -38,9 +47,11 @@ describe("<TextualBody />", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
defaultMatrixClient = getMockClientWithEventEmitter({
|
defaultMatrixClient = getMockClientWithEventEmitter({
|
||||||
getRoom: () => defaultRoom,
|
getRoom: () => defaultRoom,
|
||||||
|
getRooms: () => [defaultRoom],
|
||||||
getAccountData: (): MatrixEvent | undefined => undefined,
|
getAccountData: (): MatrixEvent | undefined => undefined,
|
||||||
isGuest: () => false,
|
isGuest: () => false,
|
||||||
mxcUrlToHttp: (s: string) => s,
|
mxcUrlToHttp: (s: string) => s,
|
||||||
|
getUserId: () => "@user:example.com",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -116,17 +127,7 @@ describe("<TextualBody />", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("simple message renders as expected", () => {
|
it("simple message renders as expected", () => {
|
||||||
const ev = mkEvent({
|
const ev = mkRoomTextMessage("this is a plaintext message");
|
||||||
type: "m.room.message",
|
|
||||||
room: "room_id",
|
|
||||||
user: "sender",
|
|
||||||
content: {
|
|
||||||
body: "this is a plaintext message",
|
|
||||||
msgtype: "m.text",
|
|
||||||
},
|
|
||||||
event: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { container } = getComponent({ mxEvent: ev });
|
const { container } = getComponent({ mxEvent: ev });
|
||||||
expect(container).toHaveTextContent(ev.getContent().body);
|
expect(container).toHaveTextContent(ev.getContent().body);
|
||||||
const content = container.querySelector(".mx_EventTile_body");
|
const content = container.querySelector(".mx_EventTile_body");
|
||||||
|
@ -135,17 +136,7 @@ describe("<TextualBody />", () => {
|
||||||
|
|
||||||
// If pills were rendered within a Portal/same shadow DOM then it'd be easier to test
|
// If pills were rendered within a Portal/same shadow DOM then it'd be easier to test
|
||||||
it("linkification get applied correctly into the DOM", () => {
|
it("linkification get applied correctly into the DOM", () => {
|
||||||
const ev = mkEvent({
|
const ev = mkRoomTextMessage("Visit https://matrix.org/");
|
||||||
type: "m.room.message",
|
|
||||||
room: "room_id",
|
|
||||||
user: "sender",
|
|
||||||
content: {
|
|
||||||
body: "Visit https://matrix.org/",
|
|
||||||
msgtype: "m.text",
|
|
||||||
},
|
|
||||||
event: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { container } = getComponent({ mxEvent: ev });
|
const { container } = getComponent({ mxEvent: ev });
|
||||||
expect(container).toHaveTextContent(ev.getContent().body);
|
expect(container).toHaveTextContent(ev.getContent().body);
|
||||||
const content = container.querySelector(".mx_EventTile_body");
|
const content = container.querySelector(".mx_EventTile_body");
|
||||||
|
@ -155,6 +146,24 @@ describe("<TextualBody />", () => {
|
||||||
"https://matrix.org/</a></span>",
|
"https://matrix.org/</a></span>",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("pillification of MXIDs get applied correctly into the DOM", () => {
|
||||||
|
const ev = mkRoomTextMessage("Chat with @user:example.com");
|
||||||
|
const { container } = getComponent({ mxEvent: ev });
|
||||||
|
const content = container.querySelector(".mx_EventTile_body");
|
||||||
|
expect(content.innerHTML).toMatchInlineSnapshot(
|
||||||
|
`"Chat with <span><bdi><a class="mx_Pill mx_UserPill"><img class="mx_BaseAvatar mx_BaseAvatar_image" src="mxc://avatar.url/image.png" style="width: 16px; height: 16px;" alt="" data-testid="avatar-img" aria-hidden="true"><span class="mx_Pill_linkText">Member</span></a></bdi></span>"`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("pillification of room aliases get applied correctly into the DOM", () => {
|
||||||
|
const ev = mkRoomTextMessage("Visit #room:example.com");
|
||||||
|
const { container } = getComponent({ mxEvent: ev });
|
||||||
|
const content = container.querySelector(".mx_EventTile_body");
|
||||||
|
expect(content.innerHTML).toMatchInlineSnapshot(
|
||||||
|
`"Visit <span><bdi><a class="mx_Pill mx_RoomPill" href="https://matrix.to/#/#room:example.com"><span class="mx_Pill_linkText">#room:example.com</span></a></bdi></span>"`,
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("renders formatted m.text correctly", () => {
|
describe("renders formatted m.text correctly", () => {
|
||||||
|
@ -382,17 +391,7 @@ describe("<TextualBody />", () => {
|
||||||
});
|
});
|
||||||
DMRoomMap.makeShared();
|
DMRoomMap.makeShared();
|
||||||
|
|
||||||
const ev = mkEvent({
|
const ev = mkRoomTextMessage("Visit https://matrix.org/");
|
||||||
type: "m.room.message",
|
|
||||||
room: "room_id",
|
|
||||||
user: "sender",
|
|
||||||
content: {
|
|
||||||
body: "Visit https://matrix.org/",
|
|
||||||
msgtype: "m.text",
|
|
||||||
},
|
|
||||||
event: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { container, rerender } = getComponent(
|
const { container, rerender } = getComponent(
|
||||||
{ mxEvent: ev, showUrlPreview: true, onHeightChanged: jest.fn() },
|
{ mxEvent: ev, showUrlPreview: true, onHeightChanged: jest.fn() },
|
||||||
matrixClient,
|
matrixClient,
|
||||||
|
|
Loading…
Reference in a new issue