diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index a4cc689349a..3ffe63a235d 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -114,7 +114,7 @@ interface IProps extends MenuProps { // True if the menu is being used as a right click menu rightClick?: boolean; // The Relations model from the JS SDK for reactions to `mxEvent` - reactions?: Relations | null; + reactions?: Relations | null | undefined; // A permalink to this event or an href of an anchor element the user has clicked link?: string; @@ -556,7 +556,7 @@ export default class MessageContextMenu extends React.Component } let jumpToRelatedEventButton: JSX.Element | undefined; - const relatedEventId = mxEvent.relationEventId; + const relatedEventId = mxEvent.getWireContent()?.["m.relates_to"]?.event_id; if (relatedEventId && SettingsStore.getValue("developerMode")) { jumpToRelatedEventButton = ( = { [LabGroup.VoiceAndVideo]: _td("Voice & Video"), [LabGroup.Moderation]: _td("Moderation"), [LabGroup.Analytics]: _td("Analytics"), + [LabGroup.MessagePreviews]: _td("Message Previews"), [LabGroup.Themes]: _td("Themes"), [LabGroup.Encryption]: _td("Encryption"), [LabGroup.Experimental]: _td("Experimental"), @@ -296,6 +298,22 @@ export const SETTINGS: { [setting: string]: ISetting } = { supportedLevels: LEVELS_FEATURE, default: false, }, + "feature_roomlist_preview_reactions_dms": { + isFeature: true, + labsGroup: LabGroup.MessagePreviews, + displayName: _td("Show message previews for reactions in DMs"), + supportedLevels: LEVELS_FEATURE, + default: false, + // this option is a subset of `feature_roomlist_preview_reactions_all` so disable it when that one is enabled + controller: new IncompatibleController("feature_roomlist_preview_reactions_all"), + }, + "feature_roomlist_preview_reactions_all": { + isFeature: true, + labsGroup: LabGroup.MessagePreviews, + displayName: _td("Show message previews for reactions in all rooms"), + supportedLevels: LEVELS_FEATURE, + default: false, + }, "feature_dehydration": { isFeature: true, labsGroup: LabGroup.Encryption, diff --git a/src/stores/room-list/previews/ReactionEventPreview.ts b/src/stores/room-list/previews/ReactionEventPreview.ts index a9f06581415..6af7ebab702 100644 --- a/src/stores/room-list/previews/ReactionEventPreview.ts +++ b/src/stores/room-list/previews/ReactionEventPreview.ts @@ -18,39 +18,37 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { IPreview } from "./IPreview"; import { TagID } from "../models"; -import { getSenderName, isSelf } from "./utils"; +import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils"; import { _t } from "../../../languageHandler"; -import { MatrixClientPeg } from "../../../MatrixClientPeg"; -import { MessagePreviewStore } from "../MessagePreviewStore"; +import SettingsStore from "../../../settings/SettingsStore"; +import DMRoomMap from "../../../utils/DMRoomMap"; export class ReactionEventPreview implements IPreview { public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string | null { + const showDms = SettingsStore.getValue("feature_roomlist_preview_reactions_dms"); + const showAll = SettingsStore.getValue("feature_roomlist_preview_reactions_all"); + const roomId = event.getRoomId(); if (!roomId) return null; // not a room event + // If we're not showing all reactions, see if we're showing DMs instead + if (!showAll) { + // If we're not showing reactions on DMs, or we are and the room isn't a DM, skip + if (!(showDms && DMRoomMap.shared().getUserIdForRoomId(roomId))) { + return null; + } + } + const relation = event.getRelation(); if (!relation) return null; // invalid reaction (probably redacted) const reaction = relation.key; if (!reaction) return null; // invalid reaction (unknown format) - const cli = MatrixClientPeg.get(); - const room = cli?.getRoom(roomId); - const relatedEvent = relation.event_id ? room?.findEventById(relation.event_id) : null; - if (!relatedEvent) return null; - - const message = MessagePreviewStore.instance.generatePreviewForEvent(relatedEvent); - if (isSelf(event)) { - return _t("You reacted %(reaction)s to %(message)s", { - reaction, - message, - }); + if (isThread || isSelf(event) || !shouldPrefixMessagesIn(roomId, tagId)) { + return reaction; + } else { + return _t("%(senderName)s: %(reaction)s", { senderName: getSenderName(event), reaction }); } - - return _t("%(sender)s reacted %(reaction)s to %(message)s", { - sender: getSenderName(event), - reaction, - message, - }); } } diff --git a/src/stores/room-list/previews/utils.ts b/src/stores/room-list/previews/utils.ts index ec8ddb28e3b..2dfa75966e8 100644 --- a/src/stores/room-list/previews/utils.ts +++ b/src/stores/room-list/previews/utils.ts @@ -20,7 +20,7 @@ import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { DefaultTagID, TagID } from "../models"; export function isSelf(event: MatrixEvent): boolean { - const selfUserId = MatrixClientPeg.get().getSafeUserId(); + const selfUserId = MatrixClientPeg.get().getUserId(); if (event.getType() === "m.room.member") { return event.getStateKey() === selfUserId; } @@ -37,5 +37,5 @@ export function shouldPrefixMessagesIn(roomId: string, tagId?: TagID): boolean { } export function getSenderName(event: MatrixEvent): string { - return event.sender?.name ?? event.getSender() ?? ""; + return event.sender ? event.sender.name : event.getSender() || ""; } diff --git a/test/components/views/settings/tabs/user/LabsUserSettingsTab-test.tsx b/test/components/views/settings/tabs/user/LabsUserSettingsTab-test.tsx index a8001481f64..ddf6105274c 100644 --- a/test/components/views/settings/tabs/user/LabsUserSettingsTab-test.tsx +++ b/test/components/views/settings/tabs/user/LabsUserSettingsTab-test.tsx @@ -70,7 +70,7 @@ describe("", () => { const { container } = render(getComponent()); const labsSections = container.getElementsByClassName("mx_SettingsTab_section"); - expect(labsSections).toHaveLength(11); + expect(labsSections).toHaveLength(12); }); it("allow setting a labs flag which requires unstable support once support is confirmed", async () => { diff --git a/test/stores/room-list/previews/PollStartEventPreview-test.ts b/test/stores/room-list/previews/PollStartEventPreview-test.ts index dc9bcabfe3f..324f42d7b68 100644 --- a/test/stores/room-list/previews/PollStartEventPreview-test.ts +++ b/test/stores/room-list/previews/PollStartEventPreview-test.ts @@ -22,7 +22,6 @@ import { makePollStartEvent } from "../../../test-utils"; jest.spyOn(MatrixClientPeg, "get").mockReturnValue({ getUserId: () => "@me:example.com", - getSafeUserId: () => "@me:example.com", } as unknown as MatrixClient); describe("PollStartEventPreview", () => { diff --git a/test/stores/room-list/previews/ReactionEventPreview-test.ts b/test/stores/room-list/previews/ReactionEventPreview-test.ts deleted file mode 100644 index e49dba342c3..00000000000 --- a/test/stores/room-list/previews/ReactionEventPreview-test.ts +++ /dev/null @@ -1,139 +0,0 @@ -/* -Copyright 2023 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. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import { RelationType, Room, RoomMember } from "matrix-js-sdk/src/matrix"; -import { mocked } from "jest-mock"; - -import { mkEvent, stubClient } from "../../../test-utils"; -import { ReactionEventPreview } from "../../../../src/stores/room-list/previews/ReactionEventPreview"; -import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; - -describe("ReactionEventPreview", () => { - const preview = new ReactionEventPreview(); - const userId = "@user:example.com"; - const roomId = "!room:example.com"; - - beforeAll(() => { - stubClient(); - }); - - describe("getTextFor", () => { - it("should return null for non-relations", () => { - const event = mkEvent({ - event: true, - content: {}, - user: userId, - type: "m.room.message", - room: roomId, - }); - expect(preview.getTextFor(event)).toBeNull(); - }); - - it("should return null for non-reactions", () => { - const event = mkEvent({ - event: true, - content: { - "body": "", - "m.relates_to": { - rel_type: RelationType.Thread, - event_id: "$foo:bar", - }, - }, - user: userId, - type: "m.room.message", - room: roomId, - }); - expect(preview.getTextFor(event)).toBeNull(); - }); - - it("should use 'You' for your own reactions", () => { - const cli = MatrixClientPeg.get(); - const room = new Room(roomId, cli, userId); - mocked(cli.getRoom).mockReturnValue(room); - - const message = mkEvent({ - event: true, - content: { - "body": "duck duck goose", - "m.relates_to": { - rel_type: RelationType.Thread, - event_id: "$foo:bar", - }, - }, - user: userId, - type: "m.room.message", - room: roomId, - }); - - room.getUnfilteredTimelineSet().addLiveEvent(message, {}); - - const event = mkEvent({ - event: true, - content: { - "m.relates_to": { - rel_type: RelationType.Annotation, - key: "🪿", - event_id: message.getId(), - }, - }, - user: cli.getSafeUserId(), - type: "m.reaction", - room: roomId, - }); - expect(preview.getTextFor(event)).toMatchInlineSnapshot(`"You reacted 🪿 to duck duck goose"`); - }); - - it("should use display name for your others' reactions", () => { - const cli = MatrixClientPeg.get(); - const room = new Room(roomId, cli, userId); - mocked(cli.getRoom).mockReturnValue(room); - - const message = mkEvent({ - event: true, - content: { - "body": "duck duck goose", - "m.relates_to": { - rel_type: RelationType.Thread, - event_id: "$foo:bar", - }, - }, - user: userId, - type: "m.room.message", - room: roomId, - }); - - room.getUnfilteredTimelineSet().addLiveEvent(message, {}); - - const event = mkEvent({ - event: true, - content: { - "m.relates_to": { - rel_type: RelationType.Annotation, - key: "🪿", - event_id: message.getId(), - }, - }, - user: userId, - type: "m.reaction", - room: roomId, - }); - event.sender = new RoomMember(roomId, userId); - event.sender.name = "Bob"; - - expect(preview.getTextFor(event)).toMatchInlineSnapshot(`"Bob reacted 🪿 to duck duck goose"`); - }); - }); -});