Move reaction message previews out of labs (#10601)

* Update reaction message previews to match designs

* Delabs reaction message previews

* tsc strict

* Iterate

* Add test

* Iterate
This commit is contained in:
Michael Telatynski 2023-05-05 08:45:14 +01:00 committed by GitHub
parent 1f4d857283
commit 4dd214506b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 167 additions and 45 deletions

View File

@ -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;
@ -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

View File

@ -915,7 +915,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",
@ -937,7 +938,6 @@
"Voice & Video": "Voice & Video",
"Moderation": "Moderation",
"Analytics": "Analytics",
"Message Previews": "Message Previews",
"Themes": "Themes",
"Encryption": "Encryption",
"Experimental": "Experimental",
@ -963,8 +963,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",

View File

@ -84,7 +84,6 @@ export enum LabGroup {
VoiceAndVideo,
Moderation,
Analytics,
MessagePreviews,
Themes,
Encryption,
Experimental,
@ -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"),
@ -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,

View File

@ -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", {
reaction,
message,
});
}
return _t("%(sender)s reacted %(reaction)s to %(message)s", {
sender: getSenderName(event),
reaction,
message,
});
}
}

View File

@ -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;
}
@ -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() ?? "";
}

View File

@ -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 () => {

View File

@ -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", () => {

View File

@ -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", () => {
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"`);
});
});
});