diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx index 32aab5d6e98..6b4f737fa03 100644 --- a/src/components/views/dialogs/ForwardDialog.tsx +++ b/src/components/views/dialogs/ForwardDialog.tsx @@ -58,6 +58,10 @@ import { getKeyBindingsManager } from "../../../KeyBindingsManager"; import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts"; import { OverflowTileView } from "../rooms/OverflowTileView"; import { attachMentions } from "../../../utils/messages"; +import { CommandPartCreator } from "../../../editor/parts"; +import SettingsStore from "../../../settings/SettingsStore"; +import { parseEvent } from "../../../editor/deserialize"; +import EditorModel from "../../../editor/model"; const AVATAR_SIZE = 30; @@ -184,13 +188,13 @@ const Entry: React.FC> = ({ room, type, content, matrixClient: * 1. Strip all relations. * 2. Convert location events into a static pin-drop location share, * and remove description from self-location shares. - * 3. Pass through attachMentions() to strip mentions (as no EditorModel is present to recalculate from). + * 3. Parse the event back into an EditorModel and recalculate mentions. * * @param event - The MatrixEvent to transform. - * @param userId - Current user MXID (passed through to attachMentions()). + * @param cli - The MatrixClient (used for recalculation of mentions). * @returns The transformed event type and content. */ -const transformEvent = (event: MatrixEvent, userId: string): { type: string; content: IContent } => { +const transformEvent = (event: MatrixEvent, cli: MatrixClient): { type: string; content: IContent } => { const { // eslint-disable-next-line @typescript-eslint/no-unused-vars "m.relates_to": _, // strip relations - in future we will attach a relation pointing at the original event @@ -225,12 +229,17 @@ const transformEvent = (event: MatrixEvent, userId: string): { type: string; con }; } - // Mentions can leak information about the context of the original message, - // so pass through attachMentions() to recalculate mentions. - // Currently, this strips all mentions (forces an empty m.mentions), - // as there is no EditorModel to parse pills from. - // Future improvements could actually recalculate mentions based on the message body. - attachMentions(userId, content, null, undefined); + // Mentions can leak information about the context of the original message, so: + // 1. Parse the event's message body back into an EditorModel, then + // 2. Pass through attachMentions() to recalculate mentions. + const room = cli.getRoom(event.getRoomId())!; + const partCreator = new CommandPartCreator(room, cli); + const parts = parseEvent(event, partCreator, { + shouldEscape: SettingsStore.getValue("MessageComposerInput.useMarkdown"), + }); + const model = new EditorModel(parts, partCreator); // Temporary EditorModel to pass through + const userId = cli.getSafeUserId(); + attachMentions(userId, content, model, undefined); return { type, content }; }; @@ -242,7 +251,7 @@ const ForwardDialog: React.FC = ({ matrixClient: cli, event, permalinkCr cli.getProfileInfo(userId).then((info) => setProfileInfo(info)); }, [cli, userId]); - const { type, content } = transformEvent(event, userId); + const { type, content } = transformEvent(event, cli); // For the message preview we fake the sender as ourselves const mockEvent = new MatrixEvent({ diff --git a/test/unit-tests/components/views/dialogs/ForwardDialog-test.tsx b/test/unit-tests/components/views/dialogs/ForwardDialog-test.tsx index 370f612c9cf..7011bc780cd 100644 --- a/test/unit-tests/components/views/dialogs/ForwardDialog-test.tsx +++ b/test/unit-tests/components/views/dialogs/ForwardDialog-test.tsx @@ -249,34 +249,56 @@ describe("ForwardDialog", () => { expect(secondButton.getAttribute("aria-disabled")).toBeFalsy(); }); - it("strips mentions from forwarded messages", async () => { - const messageWithMention = mkEvent({ - type: "m.room.message", - room: sourceRoom, - user: "@bob:example.org", - content: { - "msgtype": "m.text", - "body": "Hi @alice:example.org", - "m.mentions": { - user_ids: ["@alice:example.org"], - }, - }, - event: true, - }); - - const { container } = mountForwardDialog(messageWithMention); + describe("Mention recalculation", () => { const roomId = "a"; + const sendClick = (container: HTMLElement): void => + act(() => { + const sendButton = container.querySelector(".mx_ForwardList_sendButton"); + fireEvent.click(sendButton!); + }); + const makeMessage = (body: string, mentions: object, formattedBody?: string) => { + return mkEvent({ + type: "m.room.message", + room: sourceRoom, + user: "@bob:example.org", + content: { + "msgtype": "m.text", + "body": body, + "m.mentions": mentions, + ...(formattedBody && { + format: "org.matrix.custom.html", + formatted_body: formattedBody, + }), + }, + event: true, + }); + }; - // Click the send button. - act(() => { - const sendButton = container.querySelector(".mx_ForwardList_sendButton"); - fireEvent.click(sendButton!); + it("strips extra mentions", async () => { + const message = makeMessage("Hi Alice", { user_ids: [aliceId] }); + const { container } = mountForwardDialog(message); + sendClick(container); + // Expected content should have mentions empty. + expect(mockClient.sendEvent).toHaveBeenCalledWith(roomId, message.getType(), { + ...message.getContent(), + "m.mentions": {}, + }); }); - // Expected content should have mentions empty. - expect(mockClient.sendEvent).toHaveBeenCalledWith(roomId, messageWithMention.getType(), { - ...messageWithMention.getContent(), - "m.mentions": {}, + // TODO: mock room membership + it("recalculates mention pills", async () => { + const message = makeMessage( + "Hi Alice", + { user_ids: [aliceId] }, + `Hi Alice`, + ); + const { container } = mountForwardDialog(message); + sendClick(container); + // Expected content should have mentions empty. + expect(mockClient.sendEvent).toHaveBeenCalledWith(roomId, message.getType(), { + ...message.getContent(), + "m.mentions": { user_ids: [aliceId] }, + }); }); });