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

Move reaction message previews out of labs #10601

Merged
merged 7 commits into from
May 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/components/views/context_menus/MessageContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 | undefined;
reactions?: Relations | null;
// A permalink to this event or an href of an anchor element the user has clicked
link?: string;

Expand Down Expand Up @@ -556,7 +556,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
}

let jumpToRelatedEventButton: JSX.Element | undefined;
const relatedEventId = mxEvent.getWireContent()?.["m.relates_to"]?.event_id;
const relatedEventId = mxEvent.relationEventId;
if (relatedEventId && SettingsStore.getValue("developerMode")) {
jumpToRelatedEventButton = (
<IconizedContextMenuOption
Expand Down
6 changes: 2 additions & 4 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -911,7 +911,8 @@
"%(senderName)s is calling": "%(senderName)s is calling",
"* %(senderName)s %(emote)s": "* %(senderName)s %(emote)s",
"%(senderName)s: %(message)s": "%(senderName)s: %(message)s",
"%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s",
"You reacted %(reaction)s to %(message)s": "You reacted %(reaction)s to %(message)s",
"%(sender)s reacted %(reaction)s to %(message)s": "%(sender)s reacted %(reaction)s to %(message)s",
"%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s",
"Threads": "Threads",
"Back to chat": "Back to chat",
Expand All @@ -933,7 +934,6 @@
"Voice & Video": "Voice & Video",
"Moderation": "Moderation",
"Analytics": "Analytics",
"Message Previews": "Message Previews",
"Themes": "Themes",
"Encryption": "Encryption",
"Experimental": "Experimental",
Expand All @@ -959,8 +959,6 @@
"New ways to ignore people": "New ways to ignore people",
"Currently experimental.": "Currently experimental.",
"Support adding custom themes": "Support adding custom themes",
"Show message previews for reactions in DMs": "Show message previews for reactions in DMs",
"Show message previews for reactions in all rooms": "Show message previews for reactions in all rooms",
"Offline encrypted messaging using dehydrated devices": "Offline encrypted messaging using dehydrated devices",
"Show current avatar and name for users in message history": "Show current avatar and name for users in message history",
"Show HTML representation of room topics": "Show HTML representation of room topics",
Expand Down
18 changes: 0 additions & 18 deletions src/settings/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ export enum LabGroup {
VoiceAndVideo,
Moderation,
Analytics,
MessagePreviews,
Themes,
Encryption,
Experimental,
Expand All @@ -105,7 +104,6 @@ export const labGroupNames: Record<LabGroup, string> = {
[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"),
Expand Down Expand Up @@ -298,22 +296,6 @@ 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,
Expand Down
38 changes: 20 additions & 18 deletions src/stores/room-list/previews/ReactionEventPreview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,37 +18,39 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";

import { IPreview } from "./IPreview";
import { TagID } from "../models";
import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils";
import { getSenderName, isSelf } from "./utils";
import { _t } from "../../../languageHandler";
import SettingsStore from "../../../settings/SettingsStore";
import DMRoomMap from "../../../utils/DMRoomMap";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { MessagePreviewStore } from "../MessagePreviewStore";

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)

if (isThread || isSelf(event) || !shouldPrefixMessagesIn(roomId, tagId)) {
return reaction;
} else {
return _t("%(senderName)s: %(reaction)s", { senderName: getSenderName(event), reaction });
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", {
t3chguy marked this conversation as resolved.
Show resolved Hide resolved
reaction,
message,
});
}

return _t("%(sender)s reacted %(reaction)s to %(message)s", {
sender: getSenderName(event),
reaction,
message,
});
}
}
4 changes: 2 additions & 2 deletions src/stores/room-list/previews/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { DefaultTagID, TagID } from "../models";

export function isSelf(event: MatrixEvent): boolean {
const selfUserId = MatrixClientPeg.get().getUserId();
const selfUserId = MatrixClientPeg.get().getSafeUserId();
if (event.getType() === "m.room.member") {
return event.getStateKey() === selfUserId;
}
Expand All @@ -37,5 +37,5 @@ export function shouldPrefixMessagesIn(roomId: string, tagId?: TagID): boolean {
}

export function getSenderName(event: MatrixEvent): string {
return event.sender ? event.sender.name : event.getSender() || "";
return event.sender?.name ?? event.getSender() ?? "";
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ describe("<LabsUserSettingsTab />", () => {
const { container } = render(getComponent());

const labsSections = container.getElementsByClassName("mx_SettingsTab_section");
expect(labsSections).toHaveLength(12);
expect(labsSections).toHaveLength(11);
});

it("allow setting a labs flag which requires unstable support once support is confirmed", async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { makePollStartEvent } from "../../../test-utils";

jest.spyOn(MatrixClientPeg, "get").mockReturnValue({
getUserId: () => "@me:example.com",
getSafeUserId: () => "@me:example.com",
} as unknown as MatrixClient);

describe("PollStartEventPreview", () => {
Expand Down
139 changes: 139 additions & 0 deletions test/stores/room-list/previews/ReactionEventPreview-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
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", () => {
t3chguy marked this conversation as resolved.
Show resolved Hide resolved
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"`);
});
});
});