diff --git a/src/stores/room-list-v3/RoomListStoreV3.ts b/src/stores/room-list-v3/RoomListStoreV3.ts index df7246e95c..15521ddd3f 100644 --- a/src/stores/room-list-v3/RoomListStoreV3.ts +++ b/src/stores/room-list-v3/RoomListStoreV3.ts @@ -28,11 +28,22 @@ import { FavouriteFilter } from "./skip-list/filters/FavouriteFilter"; import { UnreadFilter } from "./skip-list/filters/UnreadFilter"; import { PeopleFilter } from "./skip-list/filters/PeopleFilter"; import { RoomsFilter } from "./skip-list/filters/RoomsFilter"; +import { InvitesFilter } from "./skip-list/filters/InvitesFilter"; +import { MentionsFilter } from "./skip-list/filters/MentionsFilter"; +import { LowPriorityFilter } from "./skip-list/filters/LowPriorityFilter"; /** * These are the filters passed to the room skip list. */ -const FILTERS = [new FavouriteFilter(), new UnreadFilter(), new PeopleFilter(), new RoomsFilter()]; +const FILTERS = [ + new FavouriteFilter(), + new UnreadFilter(), + new PeopleFilter(), + new RoomsFilter(), + new InvitesFilter(), + new MentionsFilter(), + new LowPriorityFilter(), +]; /** * This store allows for fast retrieval of the room list in a sorted and filtered manner. diff --git a/src/stores/room-list-v3/skip-list/filters/InvitesFilter.ts b/src/stores/room-list-v3/skip-list/filters/InvitesFilter.ts new file mode 100644 index 0000000000..fb9fff9b44 --- /dev/null +++ b/src/stores/room-list-v3/skip-list/filters/InvitesFilter.ts @@ -0,0 +1,20 @@ +/* +Copyright 2025 New Vector Ltd. +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +import { type Room, KnownMembership } from "matrix-js-sdk/src/matrix"; + +import type { Filter } from "."; +import { FilterKey } from "."; + +export class InvitesFilter implements Filter { + public matches(room: Room): boolean { + return room.getMyMembership() === KnownMembership.Invite; + } + + public get key(): FilterKey.InvitesFilter { + return FilterKey.InvitesFilter; + } +} diff --git a/src/stores/room-list-v3/skip-list/filters/LowPriorityFilter.ts b/src/stores/room-list-v3/skip-list/filters/LowPriorityFilter.ts new file mode 100644 index 0000000000..da47761d6e --- /dev/null +++ b/src/stores/room-list-v3/skip-list/filters/LowPriorityFilter.ts @@ -0,0 +1,20 @@ +/* +Copyright 2025 New Vector Ltd. +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +import type { Room } from "matrix-js-sdk/src/matrix"; +import type { Filter } from "."; +import { FilterKey } from "."; +import { DefaultTagID } from "../../../room-list/models"; + +export class LowPriorityFilter implements Filter { + public matches(room: Room): boolean { + return !!room.tags[DefaultTagID.LowPriority]; + } + + public get key(): FilterKey.LowPriorityFilter { + return FilterKey.LowPriorityFilter; + } +} diff --git a/src/stores/room-list-v3/skip-list/filters/MentionsFilter.ts b/src/stores/room-list-v3/skip-list/filters/MentionsFilter.ts new file mode 100644 index 0000000000..8897e766d4 --- /dev/null +++ b/src/stores/room-list-v3/skip-list/filters/MentionsFilter.ts @@ -0,0 +1,20 @@ +/* +Copyright 2025 New Vector Ltd. +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +import type { Room } from "matrix-js-sdk/src/matrix"; +import type { Filter } from "."; +import { FilterKey } from "."; +import { RoomNotificationStateStore } from "../../../notifications/RoomNotificationStateStore"; + +export class MentionsFilter implements Filter { + public matches(room: Room): boolean { + return RoomNotificationStateStore.instance.getRoomState(room).hasMentions; + } + + public get key(): FilterKey.MentionsFilter { + return FilterKey.MentionsFilter; + } +} diff --git a/src/stores/room-list-v3/skip-list/filters/index.ts b/src/stores/room-list-v3/skip-list/filters/index.ts index 29bcabb27e..e4c65167b3 100644 --- a/src/stores/room-list-v3/skip-list/filters/index.ts +++ b/src/stores/room-list-v3/skip-list/filters/index.ts @@ -11,6 +11,9 @@ export const enum FilterKey { UnreadFilter, PeopleFilter, RoomsFilter, + LowPriorityFilter, + MentionsFilter, + InvitesFilter, } export interface Filter { diff --git a/test/unit-tests/stores/room-list-v3/RoomListStoreV3-test.ts b/test/unit-tests/stores/room-list-v3/RoomListStoreV3-test.ts index 6d4b81239e..ac9f820439 100644 --- a/test/unit-tests/stores/room-list-v3/RoomListStoreV3-test.ts +++ b/test/unit-tests/stores/room-list-v3/RoomListStoreV3-test.ts @@ -469,6 +469,77 @@ describe("RoomListStoreV3", () => { } }); + it("supports filtering invited rooms", async () => { + const { client, rooms } = getClientAndRooms(); + + // Let's add 5 rooms that we are invited to + const invitedRooms = getMockedRooms(client, 5); + for (const room of invitedRooms) { + room.getMyMembership = jest.fn().mockReturnValue(KnownMembership.Invite); + } + + rooms.push(...invitedRooms); + + // Let's choose 5 rooms to put in space + const { spaceRoom, roomIds } = createSpace(rooms, [6, 8, 100, 101, 102, 103, 104], client); + setupMocks(spaceRoom, roomIds); + const store = new RoomListStoreV3Class(dispatcher); + await store.start(); + + const result = store.getSortedRoomsInActiveSpace([FilterKey.InvitesFilter]); + expect(result).toHaveLength(5); + for (const room of invitedRooms) { + expect(result).toContain(room); + } + }); + + it("supports filtering by mentions", async () => { + const { client, rooms } = getClientAndRooms(); + // Let's choose 5 rooms to put in space + const { spaceRoom, roomIds } = createSpace(rooms, [6, 8, 13, 27, 75], client); + + // Let's say 8, 27 have mentions + jest.spyOn(RoomNotificationStateStore.instance, "getRoomState").mockImplementation((room) => { + const state = { + hasMentions: [rooms[8], rooms[27]].includes(room), + } as unknown as RoomNotificationState; + return state; + }); + + setupMocks(spaceRoom, roomIds); + const store = new RoomListStoreV3Class(dispatcher); + await store.start(); + + // Should only give us rooms at index 8 and 27 + const result = store.getSortedRoomsInActiveSpace([FilterKey.MentionsFilter]); + expect(result).toHaveLength(2); + for (const i of [8, 27]) { + expect(result).toContain(rooms[i]); + } + }); + + it("supports filtering low priority rooms", async () => { + const { client, rooms } = getClientAndRooms(); + // Let's choose 5 rooms to put in space + const { spaceRoom, roomIds } = createSpace(rooms, [6, 8, 13, 27, 75], client); + + // Let's say that 8, 27 an 75 are low priority rooms + [8, 27, 75].forEach((i) => { + rooms[i].tags[DefaultTagID.LowPriority] = {}; + }); + + setupMocks(spaceRoom, roomIds); + const store = new RoomListStoreV3Class(dispatcher); + await store.start(); + + // Sorted, filtered rooms should be 8, 27 and 75 + const result = store.getSortedRoomsInActiveSpace([FilterKey.LowPriorityFilter]); + expect(result).toHaveLength(3); + for (const i of [8, 27, 75]) { + expect(result).toContain(rooms[i]); + } + }); + it("supports multiple filters", async () => { const { client, rooms } = getClientAndRooms(); // Let's choose 5 rooms to put in space