Skip to content

Commit 66d9d71

Browse files
authored
Add setting to hide avatars of rooms you have been invited to. (#29497)
* Add ability to block images of rooms you have been invited to. * strings * Add tests * fix snapshot * tweaks * lint
1 parent 4e3daa5 commit 66d9d71

8 files changed

Lines changed: 185 additions & 74 deletions

File tree

Loading

src/components/views/avatars/RoomAvatar.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
/*
2-
Copyright 2024 New Vector Ltd.
2+
Copyright 2024, 2025 New Vector Ltd.
33
Copyright 2015, 2016 OpenMarket Ltd
44
55
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
66
Please see LICENSE files in the repository root for full details.
77
*/
88

99
import React, { type ComponentProps } from "react";
10-
import { type Room, RoomStateEvent, type MatrixEvent, EventType, RoomType } from "matrix-js-sdk/src/matrix";
10+
import {
11+
type Room,
12+
RoomStateEvent,
13+
type MatrixEvent,
14+
EventType,
15+
RoomType,
16+
KnownMembership,
17+
} from "matrix-js-sdk/src/matrix";
1118

1219
import BaseAvatar from "./BaseAvatar";
1320
import ImageView from "../elements/ImageView";
@@ -19,6 +26,7 @@ import { mediaFromMxc } from "../../../customisations/Media";
1926
import { type IOOBData } from "../../../stores/ThreepidInviteStore";
2027
import { LocalRoom } from "../../../models/LocalRoom";
2128
import { filterBoolean } from "../../../utils/arrays";
29+
import SettingsStore from "../../../settings/SettingsStore";
2230

2331
interface IProps extends Omit<ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url" | "onClick"> {
2432
// Room may be left unset here, but if it is,
@@ -86,6 +94,13 @@ export default class RoomAvatar extends React.Component<IProps, IState> {
8694
};
8795

8896
private static getImageUrls(props: IProps): string[] {
97+
const myMembership = props.room?.getMyMembership();
98+
if (myMembership === KnownMembership.Invite || !myMembership) {
99+
if (SettingsStore.getValue("showAvatarsOnInvites") === false) {
100+
// The user has opted out of showing avatars, so return no urls here.
101+
return [];
102+
}
103+
}
89104
let oobAvatar: string | null = null;
90105
if (props.oobData.avatarUrl) {
91106
oobAvatar = mediaFromMxc(props.oobData.avatarUrl).getThumbnailOfSourceHttp(

src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2024 New Vector Ltd.
2+
Copyright 2024, 2025 New Vector Ltd.
33
Copyright 2019-2023 The Matrix.org Foundation C.I.C.
44
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
55
@@ -116,7 +116,7 @@ const SpellCheckSection: React.FC = () => {
116116
};
117117

118118
export default class PreferencesUserSettingsTab extends React.Component<IProps, IState> {
119-
private static ROOM_LIST_SETTINGS: BooleanSettingKey[] = ["breadcrumbs"];
119+
private static ROOM_LIST_SETTINGS: BooleanSettingKey[] = ["breadcrumbs", "showAvatarsOnInvites"];
120120

121121
private static SPACES_SETTINGS: BooleanSettingKey[] = ["Spaces.allRoomsInHome"];
122122

src/i18n/strings/en_EN.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2643,6 +2643,7 @@
26432643
"inline_url_previews_room": "Enable URL previews by default for participants in this room",
26442644
"inline_url_previews_room_account": "Enable URL previews for this room (only affects you)",
26452645
"insert_trailing_colon_mentions": "Insert a trailing colon after user mentions at the start of a message",
2646+
"invite_avatars": "Show avatars of rooms you have been invited to",
26462647
"jump_to_bottom_on_send": "Jump to the bottom of the timeline when you send a message",
26472648
"key_backup": {
26482649
"backup_in_progress": "Your keys are being backed up (the first backup could take a few minutes).",

src/settings/Settings.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2024 New Vector Ltd.
2+
Copyright 2024, 2025 New Vector Ltd.
33
Copyright 2018-2024 The Matrix.org Foundation C.I.C.
44
Copyright 2017 Travis Ralston
55
@@ -312,6 +312,7 @@ export interface Settings {
312312
"lowBandwidth": IBaseSetting<boolean>;
313313
"fallbackICEServerAllowed": IBaseSetting<boolean | null>;
314314
"showImages": IBaseSetting<boolean>;
315+
"showAvatarsOnInvites": IBaseSetting<boolean>;
315316
"RoomList.preferredSorting": IBaseSetting<SortingAlgorithm>;
316317
"RightPanel.phasesGlobal": IBaseSetting<IRightPanelForRoomStored | null>;
317318
"RightPanel.phases": IBaseSetting<IRightPanelForRoomStored | null>;
@@ -1116,6 +1117,11 @@ export const SETTINGS: Settings = {
11161117
displayName: _td("settings|image_thumbnails"),
11171118
default: true,
11181119
},
1120+
"showAvatarsOnInvites": {
1121+
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
1122+
displayName: _td("settings|invite_avatars"),
1123+
default: true,
1124+
},
11191125
"RoomList.preferredSorting": {
11201126
supportedLevels: [SettingLevel.DEVICE],
11211127
default: SortingAlgorithm.Recency,

test/unit-tests/components/views/avatars/RoomAvatar-test.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2024 New Vector Ltd.
2+
Copyright 2024, 2025 New Vector Ltd.
33
Copyright 2022 The Matrix.org Foundation C.I.C.
44
55
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
@@ -17,6 +17,8 @@ import DMRoomMap from "../../../../../src/utils/DMRoomMap";
1717
import { LocalRoom } from "../../../../../src/models/LocalRoom";
1818
import * as AvatarModule from "../../../../../src/Avatar";
1919
import { DirectoryMember } from "../../../../../src/utils/direct-messages";
20+
import SettingsStore from "../../../../../src/settings/SettingsStore";
21+
import { SettingLevel } from "../../../../../src/settings/SettingLevel";
2022

2123
describe("RoomAvatar", () => {
2224
let client: MatrixClient;
@@ -41,6 +43,12 @@ describe("RoomAvatar", () => {
4143
afterEach(() => {
4244
mocked(DMRoomMap.shared().getUserIdForRoomId).mockReset();
4345
mocked(AvatarModule.defaultAvatarUrlForString).mockClear();
46+
SettingsStore.setValue(
47+
"showAvatarsOnInvites",
48+
null,
49+
SettingLevel.ACCOUNT,
50+
SettingsStore.getDefaultValue("showAvatarsOnInvites"),
51+
);
4452
});
4553

4654
it("should render as expected for a Room", () => {
@@ -64,4 +72,19 @@ describe("RoomAvatar", () => {
6472
localRoom.targets.push(new DirectoryMember({ user_id: userId }));
6573
expect(render(<RoomAvatar room={localRoom} />).container).toMatchSnapshot();
6674
});
75+
it("should render an avatar for a room the user is invited to", () => {
76+
SettingsStore.setValue("showAvatarsOnInvites", null, SettingLevel.ACCOUNT, true);
77+
const room = new Room("!room:example.com", client, client.getSafeUserId());
78+
jest.spyOn(room, "getMxcAvatarUrl").mockImplementation(() => "mxc://example.com/foobar");
79+
room.name = "test room";
80+
room.updateMyMembership("invite");
81+
expect(render(<RoomAvatar room={room} />).container).toMatchSnapshot();
82+
});
83+
it("should not render an invite avatar if the user has disabled it", () => {
84+
SettingsStore.setValue("showAvatarsOnInvites", null, SettingLevel.ACCOUNT, false);
85+
const room = new Room("!room:example.com", client, client.getSafeUserId());
86+
room.name = "test room";
87+
room.updateMyMembership("invite");
88+
expect(render(<RoomAvatar room={room} />).container).toMatchSnapshot();
89+
});
6790
});

test/unit-tests/components/views/avatars/__snapshots__/RoomAvatar-test.tsx.snap

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,44 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`RoomAvatar should not render an invite avatar if the user has disabled it 1`] = `
4+
<div>
5+
<span
6+
class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
7+
data-color="6"
8+
data-testid="avatar-img"
9+
data-type="round"
10+
role="presentation"
11+
style="--cpd-avatar-size: 36px;"
12+
>
13+
t
14+
</span>
15+
</div>
16+
`;
17+
18+
exports[`RoomAvatar should render an avatar for a room the user is invited to 1`] = `
19+
<div>
20+
<span
21+
aria-label="Avatar"
22+
class="_avatar_1qbcf_8 mx_BaseAvatar"
23+
data-color="6"
24+
data-testid="avatar-img"
25+
data-type="round"
26+
style="--cpd-avatar-size: 36px;"
27+
>
28+
<img
29+
alt=""
30+
class="_image_1qbcf_41"
31+
data-type="round"
32+
height="36px"
33+
loading="lazy"
34+
referrerpolicy="no-referrer"
35+
src="http://this.is.a.url/example.com/foobar"
36+
width="36px"
37+
/>
38+
</span>
39+
</div>
40+
`;
41+
342
exports[`RoomAvatar should render as expected for a DM room 1`] = `
443
<div>
544
<span

0 commit comments

Comments
 (0)