diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx index 2b0094dd3d..83c8c9fc11 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx @@ -89,6 +89,8 @@ export default class PreferencesUserSettingsTab extends React.Component +
+ {_t("Room directory")} + {this.renderGroup(PreferencesUserSettingsTab.ROOM_DIRECTORY_SETTINGS)} +
+
{_t("General")} {this.renderGroup(PreferencesUserSettingsTab.GENERAL_SETTINGS)} diff --git a/src/hooks/usePublicRoomDirectory.ts b/src/hooks/usePublicRoomDirectory.ts index e1bf806c0b..d4862b6e26 100644 --- a/src/hooks/usePublicRoomDirectory.ts +++ b/src/hooks/usePublicRoomDirectory.ts @@ -25,6 +25,7 @@ import SdkConfig from "../SdkConfig"; import SettingsStore from "../settings/SettingsStore"; import { Protocols } from "../utils/DirectoryUtils"; import { useLatestResult } from "./useLatestResult"; +import { useSettingValue } from "./useSettings"; export const ALL_ROOMS = "ALL_ROOMS"; const LAST_SERVER_KEY = "mx_last_room_directory_server"; @@ -38,6 +39,10 @@ export interface IPublicRoomsOpts { let thirdParty: Protocols; +const NSFW_KEYWORD = "nsfw"; +const cheapNsfwFilter = (room: IPublicRoomsChunkRoom): boolean => + !room.name?.toLocaleLowerCase().includes(NSFW_KEYWORD) && !room.topic?.toLocaleLowerCase().includes(NSFW_KEYWORD); + export const usePublicRoomDirectory = (): { ready: boolean; loading: boolean; @@ -58,6 +63,8 @@ export const usePublicRoomDirectory = (): { const [updateQuery, updateResult] = useLatestResult(setPublicRooms); + const showNsfwPublicRooms = useSettingValue("SpotlightSearch.showNsfwPublicRooms"); + async function initProtocols(): Promise { if (!MatrixClientPeg.get()) { // We may not have a client yet when invoked from welcome page @@ -108,7 +115,7 @@ export const usePublicRoomDirectory = (): { try { setLoading(true); const { chunk } = await MatrixClientPeg.get().publicRooms(opts); - updateResult(opts, chunk); + updateResult(opts, showNsfwPublicRooms ? chunk : chunk.filter(cheapNsfwFilter)); return true; } catch (e) { console.error("Could not fetch public rooms for params", opts, e); @@ -118,7 +125,7 @@ export const usePublicRoomDirectory = (): { setLoading(false); } }, - [config, updateQuery, updateResult], + [config, updateQuery, updateResult, showNsfwPublicRooms], ); useEffect(() => { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 632f50d4c1..c2a7b5e1f2 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1018,6 +1018,7 @@ "Automatic gain control": "Automatic gain control", "Echo cancellation": "Echo cancellation", "Noise suppression": "Noise suppression", + "Show NSFW content": "Show NSFW content", "Send analytics data": "Send analytics data", "Record the client name, version, and url to recognise sessions more easily in session manager": "Record the client name, version, and url to recognise sessions more easily in session manager", "Never send encrypted messages to unverified sessions from this session": "Never send encrypted messages to unverified sessions from this session", @@ -1619,6 +1620,7 @@ "Code blocks": "Code blocks", "Images, GIFs and videos": "Images, GIFs and videos", "Timeline": "Timeline", + "Room directory": "Room directory", "Enable hardware acceleration (restart %(appName)s to take effect)": "Enable hardware acceleration (restart %(appName)s to take effect)", "Autocomplete delay (ms)": "Autocomplete delay (ms)", "Read Marker lifetime (ms)": "Read Marker lifetime (ms)", diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index bc0663e640..9e9b920a8d 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -743,6 +743,11 @@ export const SETTINGS: { [setting: string]: ISetting } = { supportedLevels: [SettingLevel.ACCOUNT], default: [], // list of room IDs, most recent first }, + "SpotlightSearch.showNsfwPublicRooms": { + supportedLevels: LEVELS_ACCOUNT_SETTINGS, + displayName: _td("Show NSFW content"), + default: false, + }, "room_directory_servers": { supportedLevels: [SettingLevel.ACCOUNT], default: [], diff --git a/test/components/views/dialogs/SpotlightDialog-test.tsx b/test/components/views/dialogs/SpotlightDialog-test.tsx index d3ab12a982..08e746bb1d 100644 --- a/test/components/views/dialogs/SpotlightDialog-test.tsx +++ b/test/components/views/dialogs/SpotlightDialog-test.tsx @@ -367,4 +367,67 @@ describe("Spotlight Dialog", () => { expect(screen.queryByText("give feedback")).not.toBeInTheDocument(); }); }); + + describe("nsfw public rooms filter", () => { + const nsfwNameRoom: IPublicRoomsChunkRoom = { + room_id: "@room1:matrix.org", + name: "Room 1 [NSFW]", + topic: undefined, + world_readable: false, + num_joined_members: 1, + guest_can_join: false, + }; + + const nsfwTopicRoom: IPublicRoomsChunkRoom = { + room_id: "@room2:matrix.org", + name: "Room 2", + topic: "A room with a topic that includes nsfw", + world_readable: false, + num_joined_members: 1, + guest_can_join: false, + }; + + const potatoRoom: IPublicRoomsChunkRoom = { + room_id: "@room3:matrix.org", + name: "Potato Room 3", + topic: "Room where we discuss potatoes", + world_readable: false, + num_joined_members: 1, + guest_can_join: false, + }; + + beforeEach(() => { + mockedClient = mockClient({ rooms: [nsfwNameRoom, nsfwTopicRoom, potatoRoom], users: [testPerson] }); + SettingsStore.setValue("SpotlightSearch.showNsfwPublicRooms", null, SettingLevel.DEVICE, false); + }); + + afterAll(() => { + SettingsStore.setValue("SpotlightSearch.showNsfwPublicRooms", null, SettingLevel.DEVICE, false); + }); + + it("does not display rooms with nsfw keywords in results when showNsfwPublicRooms is falsy", async () => { + render( null} />); + + // search is debounced + jest.advanceTimersByTime(200); + await flushPromisesWithFakeTimers(); + + expect(screen.getByText(potatoRoom.name)).toBeInTheDocument(); + expect(screen.queryByText(nsfwTopicRoom.name)).not.toBeInTheDocument(); + expect(screen.queryByText(nsfwTopicRoom.name)).not.toBeInTheDocument(); + }); + + it("displays rooms with nsfw keywords in results when showNsfwPublicRooms is truthy", async () => { + SettingsStore.setValue("SpotlightSearch.showNsfwPublicRooms", null, SettingLevel.DEVICE, true); + render( null} />); + + // search is debounced + jest.advanceTimersByTime(200); + await flushPromisesWithFakeTimers(); + + expect(screen.getByText(nsfwTopicRoom.name)).toBeInTheDocument(); + expect(screen.getByText(nsfwNameRoom.name)).toBeInTheDocument(); + expect(screen.getByText(potatoRoom.name)).toBeInTheDocument(); + }); + }); });