From 39a24dbbe14306a49954229045d4c368e6889fcb Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 2 Dec 2025 16:54:55 +0000 Subject: [PATCH] Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/actions/RoomListActions.ts | 20 +----- src/components/structures/LoggedInView.tsx | 1 + .../avatars/RoomAvatarViewModel.tsx | 1 + .../right_panel/RoomSummaryCardViewModel.tsx | 6 +- .../roomlist/RoomListItemMenuViewModel.tsx | 3 +- .../roomlist/RoomListItemViewModel.tsx | 2 +- .../context_menus/RoomGeneralContextMenu.tsx | 10 ++- src/components/views/dialogs/InviteDialog.tsx | 2 +- .../RoomNotificationStateStore.ts | 2 +- .../notifications/SpaceNotificationState.ts | 4 +- .../skip-list/filters/FavouriteFilter.ts | 2 +- .../skip-list/filters/LowPriorityFilter.ts | 2 +- .../skip-list/sorters/RecencySorter.ts | 3 +- src/stores/room-list/MessagePreviewStore.ts | 2 +- .../room-list/filters/IFilterCondition.ts | 34 +++++++++ .../room-list/filters/SpaceFilterCondition.ts | 72 +++++++++++++++++++ .../room-list/filters/VisibilityProvider.ts | 48 +++++++++++++ src/stores/room-list/previews/IPreview.ts | 25 +++++++ .../previews/LegacyCallAnswerEventPreview.ts | 28 ++++++++ .../previews/LegacyCallHangupEvent.ts | 28 ++++++++ .../previews/LegacyCallInviteEventPreview.ts | 32 +++++++++ .../room-list/previews/MessageEventPreview.ts | 2 +- .../previews/PollStartEventPreview.ts | 57 +++++++++++++++ .../previews/ReactionEventPreview.ts | 48 +++++++++++++ .../room-list/previews/StickerEventPreview.ts | 27 +++++++ src/stores/room-list/previews/utils.ts | 33 +++++++++ src/stores/spaces/SpaceStore.ts | 2 +- src/utils/room/tagRoom.ts | 59 +++++++++++++-- .../avatars/RoomAvatarViewModel-test.tsx | 1 + .../RoomListItemMenuViewModel-test.tsx | 2 +- .../RoomGeneralContextMenu-test.tsx | 1 + test/unit-tests/stores/SpaceStore-test.ts | 1 + .../room-list-v3/RoomListStoreV3-test.ts | 1 + .../room-list/MessagePreviewStore-test.ts | 2 +- test/unit-tests/utils/room/tagRoom-test.ts | 2 +- 35 files changed, 515 insertions(+), 50 deletions(-) create mode 100644 src/stores/room-list/filters/IFilterCondition.ts create mode 100644 src/stores/room-list/filters/SpaceFilterCondition.ts create mode 100644 src/stores/room-list/filters/VisibilityProvider.ts create mode 100644 src/stores/room-list/previews/IPreview.ts create mode 100644 src/stores/room-list/previews/LegacyCallAnswerEventPreview.ts create mode 100644 src/stores/room-list/previews/LegacyCallHangupEvent.ts create mode 100644 src/stores/room-list/previews/LegacyCallInviteEventPreview.ts create mode 100644 src/stores/room-list/previews/PollStartEventPreview.ts create mode 100644 src/stores/room-list/previews/ReactionEventPreview.ts create mode 100644 src/stores/room-list/previews/StickerEventPreview.ts create mode 100644 src/stores/room-list/previews/utils.ts diff --git a/src/actions/RoomListActions.ts b/src/actions/RoomListActions.ts index 49abe359e0..95baf15c15 100644 --- a/src/actions/RoomListActions.ts +++ b/src/actions/RoomListActions.ts @@ -16,6 +16,7 @@ import * as Rooms from "../Rooms"; import { _t } from "../languageHandler"; import { type AsyncActionPayload } from "../dispatcher/payloads"; import ErrorDialog from "../components/views/dialogs/ErrorDialog"; +import { DefaultTagID, type TagID } from "../utils/room/tagRoom.ts"; export default class RoomListActions { /** @@ -39,28 +40,9 @@ export default class RoomListActions { room: Room, oldTag: TagID | null, newTag: TagID | null, - newIndex: number, ): AsyncActionPayload { let metaData: Parameters[2] | undefined; - // Is the tag ordered manually? - const store = RoomListStore.instance; - if (newTag && store.getTagSorting(newTag) === SortAlgorithm.Manual) { - const newList = [...store.orderedLists[newTag]]; - - newList.sort((a, b) => a.tags[newTag].order - b.tags[newTag].order); - - const indexBefore = newIndex - 1; - const indexAfter = newIndex; - - const prevOrder = indexBefore <= 0 ? 0 : newList[indexBefore].tags[newTag].order; - const nextOrder = indexAfter >= newList.length ? 1 : newList[indexAfter].tags[newTag].order; - - metaData = { - order: (prevOrder + nextOrder) / 2.0, - }; - } - return asyncAction( "RoomListActions.tagRoom", () => { diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index d84a7c1c67..8cba303da5 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -67,6 +67,7 @@ import { MatrixClientContextProvider } from "./MatrixClientContextProvider"; import { Landmark, LandmarkNavigation } from "../../accessibility/LandmarkNavigation"; import { ModuleApi } from "../../modules/Api.ts"; import { SDKContext } from "../../contexts/SDKContext.ts"; +import { DefaultTagID } from "../../utils/room/tagRoom.ts"; // We need to fetch each pinned message individually (if we don't already have it) // so each pinned message may trigger a request. Limit the number per room for sanity. diff --git a/src/components/viewmodels/avatars/RoomAvatarViewModel.tsx b/src/components/viewmodels/avatars/RoomAvatarViewModel.tsx index 2ea0baf58c..90db7d569f 100644 --- a/src/components/viewmodels/avatars/RoomAvatarViewModel.tsx +++ b/src/components/viewmodels/avatars/RoomAvatarViewModel.tsx @@ -10,6 +10,7 @@ import { useEffect, useState } from "react"; import { useTypedEventEmitter } from "../../../hooks/useEventEmitter"; import { useDmMember, usePresence, type Presence } from "../../views/avatars/WithPresenceIndicator"; +import { DefaultTagID } from "../../../utils/room/tagRoom.ts"; export enum AvatarBadgeDecoration { LowPriority = "LowPriority", diff --git a/src/components/viewmodels/right_panel/RoomSummaryCardViewModel.tsx b/src/components/viewmodels/right_panel/RoomSummaryCardViewModel.tsx index b29b7f2897..793b5ecc50 100644 --- a/src/components/viewmodels/right_panel/RoomSummaryCardViewModel.tsx +++ b/src/components/viewmodels/right_panel/RoomSummaryCardViewModel.tsx @@ -30,7 +30,7 @@ import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks" import { ReportRoomDialog } from "../../views/dialogs/ReportRoomDialog"; import { Key } from "../../../Keyboard"; import { usePinnedEvents } from "../../../hooks/usePinnedEvents"; -import { tagRoom } from "../../../utils/room/tagRoom"; +import { DefaultTagID, tagRoom } from "../../../utils/room/tagRoom"; import { inviteToRoom } from "../../../utils/room/inviteToRoom"; export interface RoomSummaryCardState { @@ -164,9 +164,7 @@ export function useRoomSummaryCardViewModel( // value to check if the user can invite to the room const canInviteToState = useEventEmitterState(room, RoomStateEvent.Update, () => canInviteTo(room)); - const roomTags = useEventEmitterState(RoomListStore.instance, LISTS_UPDATE_EVENT, () => - RoomListStore.instance.getTagsForRoom(room), - ); + const roomTags = useEventEmitterState(RoomListStore.instance, LISTS_UPDATE_EVENT, () => getTagsForRoom(room)); const isFavorite = roomTags.includes(DefaultTagID.Favourite); const isDirectMessage = useIsDirectMessage(room); diff --git a/src/components/viewmodels/roomlist/RoomListItemMenuViewModel.tsx b/src/components/viewmodels/roomlist/RoomListItemMenuViewModel.tsx index 738a05b8c3..86f2ba0250 100644 --- a/src/components/viewmodels/roomlist/RoomListItemMenuViewModel.tsx +++ b/src/components/viewmodels/roomlist/RoomListItemMenuViewModel.tsx @@ -13,14 +13,13 @@ import { useEventEmitterState } from "../../../hooks/useEventEmitter"; import { useUnreadNotifications } from "../../../hooks/useUnreadNotifications"; import { hasAccessToNotificationMenu, hasAccessToOptionsMenu } from "./utils"; import DMRoomMap from "../../../utils/DMRoomMap"; -import { DefaultTagID } from "../../../stores/room-list/models"; import { NotificationLevel } from "../../../stores/notifications/NotificationLevel"; import { shouldShowComponent } from "../../../customisations/helpers/UIComponents"; import { UIComponent } from "../../../settings/UIFeature"; import dispatcher from "../../../dispatcher/dispatcher"; import { clearRoomNotification, setMarkedUnreadState } from "../../../utils/notifications"; import PosthogTrackers from "../../../PosthogTrackers"; -import { tagRoom } from "../../../utils/room/tagRoom"; +import { DefaultTagID, tagRoom } from "../../../utils/room/tagRoom"; import { RoomNotifState } from "../../../RoomNotifs"; import { useNotificationState } from "../../../hooks/useRoomNotificationState"; diff --git a/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx b/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx index 30576e2dc2..68f37641c2 100644 --- a/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx +++ b/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx @@ -18,13 +18,13 @@ import { type RoomNotificationState } from "../../../stores/notifications/RoomNo import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; import { useMatrixClientContext } from "../../../contexts/MatrixClientContext"; import { useEventEmitter, useEventEmitterState, useTypedEventEmitter } from "../../../hooks/useEventEmitter"; -import { DefaultTagID } from "../../../stores/room-list/models"; import { useCall, useConnectionState, useParticipantCount } from "../../../hooks/useCall"; import { CallEvent, type ConnectionState } from "../../../models/Call"; import { NotificationStateEvents } from "../../../stores/notifications/NotificationState"; import DMRoomMap from "../../../utils/DMRoomMap"; import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore"; import { useMessagePreviewToggle } from "./useMessagePreviewToggle"; +import { DefaultTagID } from "../../../utils/room/tagRoom.ts"; export interface RoomListItemViewState { /** diff --git a/src/components/views/context_menus/RoomGeneralContextMenu.tsx b/src/components/views/context_menus/RoomGeneralContextMenu.tsx index 75e1500493..3bf754da26 100644 --- a/src/components/views/context_menus/RoomGeneralContextMenu.tsx +++ b/src/components/views/context_menus/RoomGeneralContextMenu.tsx @@ -19,7 +19,6 @@ import { useUnreadNotifications } from "../../../hooks/useUnreadNotifications"; import { getKeyBindingsManager } from "../../../KeyBindingsManager"; import { _t } from "../../../languageHandler"; import { NotificationLevel } from "../../../stores/notifications/NotificationLevel"; -import { DefaultTagID, type TagID } from "../../../stores/room-list/models"; import DMRoomMap from "../../../utils/DMRoomMap"; import { clearRoomNotification, setMarkedUnreadState } from "../../../utils/notifications"; import { type IProps as IContextMenuProps } from "../../structures/ContextMenu"; @@ -33,6 +32,7 @@ import { shouldShowComponent } from "../../../customisations/helpers/UIComponent import { UIComponent } from "../../../settings/UIFeature"; import { DeveloperToolsOption } from "./DeveloperToolsOption"; import { useSettingValue } from "../../../hooks/useSettings"; +import { DefaultTagID, getTagsForRoom, type TagID } from "../../../utils/room/tagRoom.ts"; export interface RoomGeneralContextMenuProps extends IContextMenuProps { room: Room; @@ -110,9 +110,7 @@ export const RoomGeneralContextMenu: React.FC = ({ ...props }) => { const cli = useContext(MatrixClientContext); - const roomTags = useEventEmitterState(RoomListStore.instance, LISTS_UPDATE_EVENT, () => - RoomListStore.instance.getTagsForRoom(room), - ); + const roomTags = useEventEmitterState(RoomListStore.instance, LISTS_UPDATE_EVENT, () => getTagsForRoom(room)); const isDm = DMRoomMap.shared().getUserIdForRoomId(room.roomId); const wrapHandler = ( handler: (ev: ButtonEvent) => void, @@ -137,10 +135,10 @@ export const RoomGeneralContextMenu: React.FC = ({ if (!cli) return; if (tagId === DefaultTagID.Favourite || tagId === DefaultTagID.LowPriority) { const inverseTag = tagId === DefaultTagID.Favourite ? DefaultTagID.LowPriority : DefaultTagID.Favourite; - const isApplied = RoomListStore.instance.getTagsForRoom(room).includes(tagId); + const isApplied = getTagsForRoom(room).includes(tagId); const removeTag = isApplied ? tagId : inverseTag; const addTag = isApplied ? null : tagId; - dis.dispatch(RoomListActions.tagRoom(cli, room, removeTag, addTag, 0)); + dis.dispatch(RoomListActions.tagRoom(cli, room, removeTag, addTag)); } else { logger.warn(`Unexpected tag ${tagId} applied to ${room.roomId}`); } diff --git a/src/components/views/dialogs/InviteDialog.tsx b/src/components/views/dialogs/InviteDialog.tsx index 509da1ccba..0c1694d72c 100644 --- a/src/components/views/dialogs/InviteDialog.tsx +++ b/src/components/views/dialogs/InviteDialog.tsx @@ -26,7 +26,6 @@ import { abbreviateUrl } from "../../../utils/UrlUtils"; import IdentityAuthClient from "../../../IdentityAuthClient"; import { type IInviteResult, inviteMultipleToRoom, showAnyInviteErrors } from "../../../RoomInvite"; import { Action } from "../../../dispatcher/actions"; -import { DefaultTagID } from "../../../stores/room-list/models"; import SettingsStore from "../../../settings/SettingsStore"; import { UIFeature } from "../../../settings/UIFeature"; import { mediaFromMxc } from "../../../customisations/Media"; @@ -63,6 +62,7 @@ import AskInviteAnywayDialog, { type UnknownProfiles } from "./AskInviteAnywayDi import { SdkContextClass } from "../../../contexts/SDKContext"; import { type UserProfilesStore } from "../../../stores/UserProfilesStore"; import InviteProgressBody from "./InviteProgressBody.tsx"; +import { DefaultTagID } from "../../../utils/room/tagRoom.ts"; // we have a number of types defined from the Matrix spec which can't reasonably be altered here. /* eslint-disable camelcase */ diff --git a/src/stores/notifications/RoomNotificationStateStore.ts b/src/stores/notifications/RoomNotificationStateStore.ts index 12447a3983..77df58b732 100644 --- a/src/stores/notifications/RoomNotificationStateStore.ts +++ b/src/stores/notifications/RoomNotificationStateStore.ts @@ -11,13 +11,13 @@ import { type Room, ClientEvent, SyncState, type EmptyObject } from "matrix-js-s import { type ActionPayload } from "../../dispatcher/payloads"; import { AsyncStoreWithClient } from "../AsyncStoreWithClient"; import defaultDispatcher, { type MatrixDispatcher } from "../../dispatcher/dispatcher"; -import { DefaultTagID, type TagID } from "../room-list/models"; import { type FetchRoomFn, ListNotificationState } from "./ListNotificationState"; import { RoomNotificationState } from "./RoomNotificationState"; import { SummarizedNotificationState } from "./SummarizedNotificationState"; import { VisibilityProvider } from "../room-list/filters/VisibilityProvider"; import { PosthogAnalytics } from "../../PosthogAnalytics"; import SettingsStore from "../../settings/SettingsStore"; +import { DefaultTagID, type TagID } from "../../utils/room/tagRoom.ts"; export const UPDATE_STATUS_INDICATOR = Symbol("update-status-indicator"); diff --git a/src/stores/notifications/SpaceNotificationState.ts b/src/stores/notifications/SpaceNotificationState.ts index f3e802bf3e..0e61a5dc65 100644 --- a/src/stores/notifications/SpaceNotificationState.ts +++ b/src/stores/notifications/SpaceNotificationState.ts @@ -12,8 +12,8 @@ import { NotificationLevel } from "./NotificationLevel"; import { arrayDiff } from "../../utils/arrays"; import { type RoomNotificationState } from "./RoomNotificationState"; import { NotificationState, NotificationStateEvents } from "./NotificationState"; -import { DefaultTagID } from "../room-list/models"; import { RoomNotificationStateStore } from "./RoomNotificationStateStore"; +import { DefaultTagID, getTagsForRoom } from "../../utils/room/tagRoom.ts"; export class SpaceNotificationState extends NotificationState { public rooms: Room[] = []; // exposed only for tests @@ -69,7 +69,7 @@ export class SpaceNotificationState extends NotificationState { this._level = NotificationLevel.None; for (const [roomId, state] of Object.entries(this.states)) { const room = this.rooms.find((r) => r.roomId === roomId); - const roomTags = room ? RoomListStore.instance.getTagsForRoom(room) : []; + const roomTags = room ? getTagsForRoom(room) : []; // We ignore unreads in LowPriority rooms, see https://github.com/vector-im/element-web/issues/16836 if (roomTags.includes(DefaultTagID.LowPriority) && state.level === NotificationLevel.Activity) continue; diff --git a/src/stores/room-list-v3/skip-list/filters/FavouriteFilter.ts b/src/stores/room-list-v3/skip-list/filters/FavouriteFilter.ts index 6af657b81e..7a09bcce07 100644 --- a/src/stores/room-list-v3/skip-list/filters/FavouriteFilter.ts +++ b/src/stores/room-list-v3/skip-list/filters/FavouriteFilter.ts @@ -7,7 +7,7 @@ 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"; +import { DefaultTagID } from "../../../../utils/room/tagRoom.ts"; export class FavouriteFilter implements Filter { public matches(room: Room): boolean { diff --git a/src/stores/room-list-v3/skip-list/filters/LowPriorityFilter.ts b/src/stores/room-list-v3/skip-list/filters/LowPriorityFilter.ts index da47761d6e..d909f02cd8 100644 --- a/src/stores/room-list-v3/skip-list/filters/LowPriorityFilter.ts +++ b/src/stores/room-list-v3/skip-list/filters/LowPriorityFilter.ts @@ -7,7 +7,7 @@ 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"; +import { DefaultTagID } from "../../../../utils/room/tagRoom.ts"; export class LowPriorityFilter implements Filter { public matches(room: Room): boolean { diff --git a/src/stores/room-list-v3/skip-list/sorters/RecencySorter.ts b/src/stores/room-list-v3/skip-list/sorters/RecencySorter.ts index 53e8ae4331..8e53b394fa 100644 --- a/src/stores/room-list-v3/skip-list/sorters/RecencySorter.ts +++ b/src/stores/room-list-v3/skip-list/sorters/RecencySorter.ts @@ -7,9 +7,8 @@ Please see LICENSE files in the repository root for full details. import type { Room } from "matrix-js-sdk/src/matrix"; import { type Sorter, SortingAlgorithm } from "."; -import { getLastTs } from "../../../room-list/algorithms/tag-sorting/RecentAlgorithm"; import { RoomNotificationStateStore } from "../../../notifications/RoomNotificationStateStore"; -import { DefaultTagID } from "../../../room-list/models"; +import { DefaultTagID } from "../../../../utils/room/tagRoom.ts"; export class RecencySorter implements Sorter { public constructor(private myUserId: string) {} diff --git a/src/stores/room-list/MessagePreviewStore.ts b/src/stores/room-list/MessagePreviewStore.ts index 4c3cad5f43..24f3d697fc 100644 --- a/src/stores/room-list/MessagePreviewStore.ts +++ b/src/stores/room-list/MessagePreviewStore.ts @@ -21,7 +21,6 @@ import { AsyncStoreWithClient } from "../AsyncStoreWithClient"; import defaultDispatcher from "../../dispatcher/dispatcher"; import { MessageEventPreview } from "./previews/MessageEventPreview"; import { PollStartEventPreview } from "./previews/PollStartEventPreview"; -import { type TagID } from "./models"; import { LegacyCallInviteEventPreview } from "./previews/LegacyCallInviteEventPreview"; import { LegacyCallAnswerEventPreview } from "./previews/LegacyCallAnswerEventPreview"; import { LegacyCallHangupEvent } from "./previews/LegacyCallHangupEvent"; @@ -30,6 +29,7 @@ import { ReactionEventPreview } from "./previews/ReactionEventPreview"; import { UPDATE_EVENT } from "../AsyncStore"; import { type IPreview } from "./previews/IPreview"; import shouldHideEvent from "../../shouldHideEvent"; +import { TagID } from "../../utils/room/tagRoom.ts"; // Emitted event for when a room's preview has changed. First argument will the room for which // the change happened. diff --git a/src/stores/room-list/filters/IFilterCondition.ts b/src/stores/room-list/filters/IFilterCondition.ts new file mode 100644 index 0000000000..8dd77fd4b9 --- /dev/null +++ b/src/stores/room-list/filters/IFilterCondition.ts @@ -0,0 +1,34 @@ +/* +Copyright 2024 New Vector Ltd. +Copyright 2020, 2021 The Matrix.org Foundation C.I.C. + +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 EventEmitter } from "events"; + +export const FILTER_CHANGED = "filter_changed"; + +/** + * A filter condition for the room list, determining if a room + * should be shown or not. + * + * All filter conditions are expected to be stable executions, + * meaning that given the same input the same answer will be + * returned (thus allowing caching). As such, filter conditions + * can, but shouldn't, do heavier logic and not worry about being + * called constantly by the room list. When the condition changes + * such that different inputs lead to different answers (such + * as a change in the user's input), this emits FILTER_CHANGED. + */ +export interface IFilterCondition extends EventEmitter { + /** + * Determines if a given room should be visible under this + * condition. + * @param room The room to check. + * @returns True if the room should be visible. + */ + isVisible(room: Room): boolean; +} diff --git a/src/stores/room-list/filters/SpaceFilterCondition.ts b/src/stores/room-list/filters/SpaceFilterCondition.ts new file mode 100644 index 0000000000..02e20e9103 --- /dev/null +++ b/src/stores/room-list/filters/SpaceFilterCondition.ts @@ -0,0 +1,72 @@ +/* +Copyright 2024 New Vector Ltd. +Copyright 2021 The Matrix.org Foundation C.I.C. + +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 { EventEmitter } from "events"; +import { type Room } from "matrix-js-sdk/src/matrix"; + +import { FILTER_CHANGED, type IFilterCondition } from "./IFilterCondition"; +import { type IDestroyable } from "../../../utils/IDestroyable"; +import SpaceStore from "../../spaces/SpaceStore"; +import { isMetaSpace, MetaSpace, type SpaceKey } from "../../spaces"; +import { setHasDiff } from "../../../utils/sets"; +import SettingsStore from "../../../settings/SettingsStore"; + +/** + * A filter condition for the room list which reveals rooms which + * are a member of a given space or if no space is selected shows: + * + Orphaned rooms (ones not in any space you are a part of) + * + All DMs + */ +export class SpaceFilterCondition extends EventEmitter implements IFilterCondition, IDestroyable { + private roomIds = new Set(); + private userIds = new Set(); + private showPeopleInSpace = true; + private space: SpaceKey = MetaSpace.Home; + + public isVisible(room: Room): boolean { + return SpaceStore.instance.isRoomInSpace(this.space, room.roomId); + } + + private onStoreUpdate = async (forceUpdate = false): Promise => { + const beforeRoomIds = this.roomIds; + // clone the set as it may be mutated by the space store internally + this.roomIds = new Set(SpaceStore.instance.getSpaceFilteredRoomIds(this.space)); + + const beforeUserIds = this.userIds; + // clone the set as it may be mutated by the space store internally + this.userIds = new Set(SpaceStore.instance.getSpaceFilteredUserIds(this.space)); + + const beforeShowPeopleInSpace = this.showPeopleInSpace; + this.showPeopleInSpace = + isMetaSpace(this.space[0]) || SettingsStore.getValue("Spaces.showPeopleInSpace", this.space); + + if ( + forceUpdate || + beforeShowPeopleInSpace !== this.showPeopleInSpace || + setHasDiff(beforeRoomIds, this.roomIds) || + setHasDiff(beforeUserIds, this.userIds) + ) { + this.emit(FILTER_CHANGED); + // XXX: Room List Store has a bug where updates to the pre-filter during a local echo of a + // tags transition seem to be ignored, so refire in the next tick to work around it + setTimeout(() => { + this.emit(FILTER_CHANGED); + }); + } + }; + + public updateSpace(space: SpaceKey): void { + SpaceStore.instance.off(this.space, this.onStoreUpdate); + SpaceStore.instance.on((this.space = space), this.onStoreUpdate); + this.onStoreUpdate(true); // initial update from the change to the space + } + + public destroy(): void { + SpaceStore.instance.off(this.space, this.onStoreUpdate); + } +} diff --git a/src/stores/room-list/filters/VisibilityProvider.ts b/src/stores/room-list/filters/VisibilityProvider.ts new file mode 100644 index 0000000000..178a1ed553 --- /dev/null +++ b/src/stores/room-list/filters/VisibilityProvider.ts @@ -0,0 +1,48 @@ +/* + * Copyright 2024 New Vector Ltd. + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * 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 { RoomListCustomisations } from "../../../customisations/RoomList"; +import { isLocalRoom } from "../../../utils/localRoom/isLocalRoom"; + +export class VisibilityProvider { + private static internalInstance: VisibilityProvider; + + private constructor() {} + + public static get instance(): VisibilityProvider { + if (!VisibilityProvider.internalInstance) { + VisibilityProvider.internalInstance = new VisibilityProvider(); + } + return VisibilityProvider.internalInstance; + } + + public isRoomVisible(room?: Room): boolean { + if (!room) { + return false; + } + + // hide space rooms as they'll be shown in the SpacePanel + if (room.isSpaceRoom()) { + return false; + } + + if (isLocalRoom(room)) { + // local rooms shouldn't show up anywhere + return false; + } + + const isVisibleFn = RoomListCustomisations.isRoomVisible; + if (isVisibleFn) { + return isVisibleFn(room); + } + + return true; // default + } +} diff --git a/src/stores/room-list/previews/IPreview.ts b/src/stores/room-list/previews/IPreview.ts new file mode 100644 index 0000000000..cb589d98f3 --- /dev/null +++ b/src/stores/room-list/previews/IPreview.ts @@ -0,0 +1,25 @@ +/* +Copyright 2024 New Vector Ltd. +Copyright 2020 The Matrix.org Foundation C.I.C. + +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 MatrixEvent } from "matrix-js-sdk/src/matrix"; + +import { type TagID } from "../../../utils/room/tagRoom.ts"; + +/** + * Represents an event preview. + */ +export interface IPreview { + /** + * Gets the text which represents the event as a preview. + * @param event The event to preview. + * @param tagId Optional. The tag where the room the event was sent in resides. + * @param isThread Optional. Whether the preview being generated is for a thread summary. + * @returns The preview. + */ + getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string | null; +} diff --git a/src/stores/room-list/previews/LegacyCallAnswerEventPreview.ts b/src/stores/room-list/previews/LegacyCallAnswerEventPreview.ts new file mode 100644 index 0000000000..8094fb46ec --- /dev/null +++ b/src/stores/room-list/previews/LegacyCallAnswerEventPreview.ts @@ -0,0 +1,28 @@ +/* +Copyright 2024 New Vector Ltd. +Copyright 2020 The Matrix.org Foundation C.I.C. + +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 MatrixEvent } from "matrix-js-sdk/src/matrix"; + +import { type IPreview } from "./IPreview"; +import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils"; +import { _t } from "../../../languageHandler"; +import { type TagID } from "../../../utils/room/tagRoom.ts"; + +export class LegacyCallAnswerEventPreview implements IPreview { + public getTextFor(event: MatrixEvent, tagId?: TagID): string { + if (shouldPrefixMessagesIn(event.getRoomId()!, tagId)) { + if (isSelf(event)) { + return _t("event_preview|m.call.answer|you"); + } else { + return _t("event_preview|m.call.answer|user", { senderName: getSenderName(event) }); + } + } else { + return _t("event_preview|m.call.answer|dm"); + } + } +} diff --git a/src/stores/room-list/previews/LegacyCallHangupEvent.ts b/src/stores/room-list/previews/LegacyCallHangupEvent.ts new file mode 100644 index 0000000000..0ed9f6d76c --- /dev/null +++ b/src/stores/room-list/previews/LegacyCallHangupEvent.ts @@ -0,0 +1,28 @@ +/* +Copyright 2024 New Vector Ltd. +Copyright 2020 The Matrix.org Foundation C.I.C. + +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 MatrixEvent } from "matrix-js-sdk/src/matrix"; + +import { type IPreview } from "./IPreview"; +import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils"; +import { _t } from "../../../languageHandler"; +import { type TagID } from "../../../utils/room/tagRoom.ts"; + +export class LegacyCallHangupEvent implements IPreview { + public getTextFor(event: MatrixEvent, tagId?: TagID): string { + if (shouldPrefixMessagesIn(event.getRoomId()!, tagId)) { + if (isSelf(event)) { + return _t("event_preview|m.call.hangup|you"); + } else { + return _t("event_preview|m.call.hangup|user", { senderName: getSenderName(event) }); + } + } else { + return _t("timeline|m.call.hangup|dm"); + } + } +} diff --git a/src/stores/room-list/previews/LegacyCallInviteEventPreview.ts b/src/stores/room-list/previews/LegacyCallInviteEventPreview.ts new file mode 100644 index 0000000000..745f719bcd --- /dev/null +++ b/src/stores/room-list/previews/LegacyCallInviteEventPreview.ts @@ -0,0 +1,32 @@ +/* +Copyright 2024 New Vector Ltd. +Copyright 2020 The Matrix.org Foundation C.I.C. + +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 MatrixEvent } from "matrix-js-sdk/src/matrix"; + +import { type IPreview } from "./IPreview"; +import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils"; +import { _t } from "../../../languageHandler"; +import { type TagID } from "../../../utils/room/tagRoom.ts"; + +export class LegacyCallInviteEventPreview implements IPreview { + public getTextFor(event: MatrixEvent, tagId?: TagID): string { + if (shouldPrefixMessagesIn(event.getRoomId()!, tagId)) { + if (isSelf(event)) { + return _t("event_preview|m.call.invite|you"); + } else { + return _t("event_preview|m.call.invite|user", { senderName: getSenderName(event) }); + } + } else { + if (isSelf(event)) { + return _t("event_preview|m.call.invite|dm_send"); + } else { + return _t("event_preview|m.call.invite|dm_receive", { senderName: getSenderName(event) }); + } + } + } +} diff --git a/src/stores/room-list/previews/MessageEventPreview.ts b/src/stores/room-list/previews/MessageEventPreview.ts index cf69e6903d..3e8bbe0f99 100644 --- a/src/stores/room-list/previews/MessageEventPreview.ts +++ b/src/stores/room-list/previews/MessageEventPreview.ts @@ -9,11 +9,11 @@ Please see LICENSE files in the repository root for full details. import { type MatrixEvent, MsgType, RelationType } from "matrix-js-sdk/src/matrix"; import { type IPreview } from "./IPreview"; -import { type TagID } from "../models"; import { _t, sanitizeForTranslation } from "../../../languageHandler"; import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils"; import { getHtmlText } from "../../../HtmlUtils"; import { stripHTMLReply, stripPlainReply } from "../../../utils/Reply"; +import { type TagID } from "../../../utils/room/tagRoom.ts"; export class MessageEventPreview implements IPreview { public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string | null { diff --git a/src/stores/room-list/previews/PollStartEventPreview.ts b/src/stores/room-list/previews/PollStartEventPreview.ts new file mode 100644 index 0000000000..22ab69c484 --- /dev/null +++ b/src/stores/room-list/previews/PollStartEventPreview.ts @@ -0,0 +1,57 @@ +/* +Copyright 2024 New Vector Ltd. +Copyright 2021 The Matrix.org Foundation C.I.C. + +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 MatrixEvent, type PollStartEventContent } from "matrix-js-sdk/src/matrix"; +import { InvalidEventError } from "matrix-js-sdk/src/extensible_events_v1/InvalidEventError"; +import { PollStartEvent } from "matrix-js-sdk/src/extensible_events_v1/PollStartEvent"; + +import { type IPreview } from "./IPreview"; +import { _t, sanitizeForTranslation } from "../../../languageHandler"; +import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils"; +import MatrixClientContext from "../../../contexts/MatrixClientContext"; +import { TagID } from "../../../utils/room/tagRoom.ts"; + +export class PollStartEventPreview implements IPreview { + public static contextType = MatrixClientContext; + declare public context: React.ContextType; + + public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string | null { + let eventContent = event.getContent(); + + if (event.isRelation("m.replace")) { + // It's an edit, generate the preview on the new text + eventContent = event.getContent()["m.new_content"]; + } + + // Check we have the information we need, and bail out if not + if (!eventContent) { + return null; + } + + try { + const poll = new PollStartEvent({ + type: event.getType(), + content: eventContent as PollStartEventContent, + }); + + let question = poll.question.text.trim(); + question = sanitizeForTranslation(question); + + if (isThread || isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId()!, tagId)) { + return question; + } else { + return _t("event_preview|m.text", { senderName: getSenderName(event), message: question }); + } + } catch (e) { + if (e instanceof InvalidEventError) { + return null; + } + throw e; // re-throw unknown errors + } + } +} diff --git a/src/stores/room-list/previews/ReactionEventPreview.ts b/src/stores/room-list/previews/ReactionEventPreview.ts new file mode 100644 index 0000000000..17b185f78a --- /dev/null +++ b/src/stores/room-list/previews/ReactionEventPreview.ts @@ -0,0 +1,48 @@ +/* +Copyright 2024 New Vector Ltd. +Copyright 2020 The Matrix.org Foundation C.I.C. + +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 MatrixEvent } from "matrix-js-sdk/src/matrix"; + +import { type IPreview } from "./IPreview"; +import { getSenderName, isSelf } from "./utils"; +import { _t } from "../../../languageHandler"; +import { MatrixClientPeg } from "../../../MatrixClientPeg"; +import { MessagePreviewStore } from "../MessagePreviewStore"; +import { TagID } from "../../../utils/room/tagRoom.ts"; + +export class ReactionEventPreview implements IPreview { + public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string | null { + const roomId = event.getRoomId(); + if (!roomId) return null; // not a room event + + const relation = event.getRelation(); + if (!relation) return null; // invalid reaction (probably redacted) + + const reaction = relation.key; + if (!reaction) return null; // invalid reaction (unknown format) + + 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("event_preview|m.reaction|you", { + reaction, + message, + }); + } + + return _t("event_preview|m.reaction|user", { + sender: getSenderName(event), + reaction, + message, + }); + } +} diff --git a/src/stores/room-list/previews/StickerEventPreview.ts b/src/stores/room-list/previews/StickerEventPreview.ts new file mode 100644 index 0000000000..95e1038487 --- /dev/null +++ b/src/stores/room-list/previews/StickerEventPreview.ts @@ -0,0 +1,27 @@ +/* +Copyright 2024 New Vector Ltd. +Copyright 2020 The Matrix.org Foundation C.I.C. + +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 MatrixEvent } from "matrix-js-sdk/src/matrix"; + +import { type IPreview } from "./IPreview"; +import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils"; +import { _t } from "../../../languageHandler"; +import { type TagID } from "../../../utils/room/tagRoom.ts"; + +export class StickerEventPreview implements IPreview { + public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string | null { + const stickerName = event.getContent()["body"]; + if (!stickerName) return null; + + if (isThread || isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId()!, tagId)) { + return stickerName; + } else { + return _t("event_preview|m.sticker", { senderName: getSenderName(event), stickerName }); + } + } +} diff --git a/src/stores/room-list/previews/utils.ts b/src/stores/room-list/previews/utils.ts new file mode 100644 index 0000000000..49012777c4 --- /dev/null +++ b/src/stores/room-list/previews/utils.ts @@ -0,0 +1,33 @@ +/* +Copyright 2024 New Vector Ltd. +Copyright 2020 The Matrix.org Foundation C.I.C. + +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 MatrixEvent } from "matrix-js-sdk/src/matrix"; + +import { MatrixClientPeg } from "../../../MatrixClientPeg"; +import { DefaultTagID, type TagID } from "../../../utils/room/tagRoom.ts"; + +export function isSelf(event: MatrixEvent): boolean { + const selfUserId = MatrixClientPeg.safeGet().getSafeUserId(); + if (event.getType() === "m.room.member") { + return event.getStateKey() === selfUserId; + } + return event.getSender() === selfUserId; +} + +export function shouldPrefixMessagesIn(roomId: string, tagId?: TagID): boolean { + if (tagId !== DefaultTagID.DM) return true; + + // We don't prefix anything in 1:1s + const room = MatrixClientPeg.safeGet().getRoom(roomId); + if (!room) return true; + return room.currentState.getJoinedMemberCount() !== 2; +} + +export function getSenderName(event: MatrixEvent): string { + return event.sender?.name ?? event.getSender() ?? ""; +} diff --git a/src/stores/spaces/SpaceStore.ts b/src/stores/spaces/SpaceStore.ts index 91f11351a3..bf4d4d1e6d 100644 --- a/src/stores/spaces/SpaceStore.ts +++ b/src/stores/spaces/SpaceStore.ts @@ -28,7 +28,6 @@ import SettingsStore from "../../settings/SettingsStore"; import DMRoomMap from "../../utils/DMRoomMap"; import { SpaceNotificationState } from "../notifications/SpaceNotificationState"; import { RoomNotificationStateStore } from "../notifications/RoomNotificationStateStore"; -import { DefaultTagID } from "../room-list/models"; import { EnhancedMap, mapDiff } from "../../utils/maps"; import { setDiff, setHasDiff } from "../../utils/sets"; import { Action } from "../../dispatcher/actions"; @@ -62,6 +61,7 @@ import { type AfterLeaveRoomPayload } from "../../dispatcher/payloads/AfterLeave import { SdkContextClass } from "../../contexts/SDKContext"; import { ModuleApi } from "../../modules/Api.ts"; import RoomListStoreV3 from "../room-list-v3/RoomListStoreV3.ts"; +import { DefaultTagID } from "../../utils/room/tagRoom.ts"; const ACTIVE_SPACE_LS_KEY = "mx_active_space"; diff --git a/src/utils/room/tagRoom.ts b/src/utils/room/tagRoom.ts index f23b1f43e0..f07365e654 100644 --- a/src/utils/room/tagRoom.ts +++ b/src/utils/room/tagRoom.ts @@ -6,12 +6,63 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import { type Room } from "matrix-js-sdk/src/matrix"; +import { JoinRule, type Room } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; -import { DefaultTagID, type TagID } from "../../stores/room-list/models"; import RoomListActions from "../../actions/RoomListActions"; import dis from "../../dispatcher/dispatcher"; +import DMRoomMap from "../DMRoomMap.ts"; +import { EffectiveMembership, getEffectiveMembership, getEffectiveMembershipTag } from "../membership.ts"; + +export enum DefaultTagID { + Invite = "im.vector.fake.invite", + Untagged = "im.vector.fake.recent", // legacy: used to just be 'recent rooms' but now it's all untagged rooms + Archived = "im.vector.fake.archived", + LowPriority = "m.lowpriority", + Favourite = "m.favourite", + DM = "im.vector.fake.direct", + Conference = "im.vector.fake.conferences", + ServerNotice = "m.server_notice", + Suggested = "im.vector.fake.suggested", +} + +export type TagID = string | DefaultTagID; + +export function getTagsForRoom(room: Room): TagID[] { + const tags: TagID[] = []; + + if (!getEffectiveMembership(room.getMyMembership())) return []; // peeked room has no tags + + const membership = getEffectiveMembershipTag(room); + + if (membership === EffectiveMembership.Invite) { + tags.push(DefaultTagID.Invite); + } else if (membership === EffectiveMembership.Leave) { + tags.push(DefaultTagID.Archived); + } else { + tags.push(...getTagsOfJoinedRoom(room)); + } + + if (!tags.length) tags.push(DefaultTagID.Untagged); + + return tags; +} + +function getTagsOfJoinedRoom(room: Room): TagID[] { + let tags = Object.keys(room.tags || {}); + + if (tags.length === 0) { + // Check to see if it's a DM if it isn't anything else + if (DMRoomMap.shared().getUserIdForRoomId(room.roomId)) { + tags = [DefaultTagID.DM]; + } + } + if (room.isCallRoom() && (room.getJoinRule() === JoinRule.Public || room.getJoinRule() === JoinRule.Knock)) { + tags.push(DefaultTagID.Conference); + } + + return tags; +} /** * Toggle tag for a given room @@ -21,10 +72,10 @@ import dis from "../../dispatcher/dispatcher"; export function tagRoom(room: Room, tagId: TagID): void { if (tagId === DefaultTagID.Favourite || tagId === DefaultTagID.LowPriority) { const inverseTag = tagId === DefaultTagID.Favourite ? DefaultTagID.LowPriority : DefaultTagID.Favourite; - const isApplied = RoomListStore.instance.getTagsForRoom(room).includes(tagId); + const isApplied = getTagsForRoom(room).includes(tagId); const removeTag = isApplied ? tagId : inverseTag; const addTag = isApplied ? null : tagId; - dis.dispatch(RoomListActions.tagRoom(room.client, room, removeTag, addTag, 0)); + dis.dispatch(RoomListActions.tagRoom(room.client, room, removeTag, addTag)); } else { logger.warn(`Unexpected tag ${tagId} applied to ${room.roomId}`); } diff --git a/test/unit-tests/components/viewmodels/avatars/RoomAvatarViewModel-test.tsx b/test/unit-tests/components/viewmodels/avatars/RoomAvatarViewModel-test.tsx index 9d4139cb4f..bc8e99ad23 100644 --- a/test/unit-tests/components/viewmodels/avatars/RoomAvatarViewModel-test.tsx +++ b/test/unit-tests/components/viewmodels/avatars/RoomAvatarViewModel-test.tsx @@ -15,6 +15,7 @@ import { import { createTestClient, mkStubRoom } from "../../../../test-utils"; import DMRoomMap from "../../../../../src/utils/DMRoomMap"; import * as PresenceIndicatorModule from "../../../../../src/components/views/avatars/WithPresenceIndicator"; +import { DefaultTagID } from "../../../../../src/utils/room/tagRoom.ts"; jest.mock("../../../../../src/utils/room/getJoinedNonFunctionalMembers", () => ({ getJoinedNonFunctionalMembers: jest.fn().mockReturnValue([]), diff --git a/test/unit-tests/components/viewmodels/roomlist/RoomListItemMenuViewModel-test.tsx b/test/unit-tests/components/viewmodels/roomlist/RoomListItemMenuViewModel-test.tsx index 1e6d393ef7..18e2577342 100644 --- a/test/unit-tests/components/viewmodels/roomlist/RoomListItemMenuViewModel-test.tsx +++ b/test/unit-tests/components/viewmodels/roomlist/RoomListItemMenuViewModel-test.tsx @@ -19,7 +19,7 @@ import DMRoomMap from "../../../../../src/utils/DMRoomMap"; import { useUnreadNotifications } from "../../../../../src/hooks/useUnreadNotifications"; import { NotificationLevel } from "../../../../../src/stores/notifications/NotificationLevel"; import { clearRoomNotification, setMarkedUnreadState } from "../../../../../src/utils/notifications"; -import { tagRoom } from "../../../../../src/utils/room/tagRoom"; +import { DefaultTagID, tagRoom } from "../../../../../src/utils/room/tagRoom"; import dispatcher from "../../../../../src/dispatcher/dispatcher"; import { useNotificationState } from "../../../../../src/hooks/useRoomNotificationState"; import { RoomNotifState } from "../../../../../src/RoomNotifs"; diff --git a/test/unit-tests/components/views/context_menus/RoomGeneralContextMenu-test.tsx b/test/unit-tests/components/views/context_menus/RoomGeneralContextMenu-test.tsx index 0d7cc187f2..7c6f2b0f41 100644 --- a/test/unit-tests/components/views/context_menus/RoomGeneralContextMenu-test.tsx +++ b/test/unit-tests/components/views/context_menus/RoomGeneralContextMenu-test.tsx @@ -27,6 +27,7 @@ import { shouldShowComponent } from "../../../../../src/customisations/helpers/U import { UIComponent } from "../../../../../src/settings/UIFeature"; import SettingsStore from "../../../../../src/settings/SettingsStore"; import { clearAllModals } from "../../../../test-utils"; +import { DefaultTagID } from "../../../../../src/utils/room/tagRoom.ts"; jest.mock("../../../../../src/customisations/helpers/UIComponents", () => ({ shouldShowComponent: jest.fn(), diff --git a/test/unit-tests/stores/SpaceStore-test.ts b/test/unit-tests/stores/SpaceStore-test.ts index 00553a1eef..770f76db16 100644 --- a/test/unit-tests/stores/SpaceStore-test.ts +++ b/test/unit-tests/stores/SpaceStore-test.ts @@ -40,6 +40,7 @@ import { MatrixClientPeg } from "../../../src/MatrixClientPeg"; import { RoomNotificationStateStore } from "../../../src/stores/notifications/RoomNotificationStateStore"; import { NotificationLevel } from "../../../src/stores/notifications/NotificationLevel"; import { storeRoomAliasInCache } from "../../../src/RoomAliasCache.ts"; +import { DefaultTagID } from "../../../src/utils/room/tagRoom.ts"; jest.useFakeTimers(); 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 61a90cb533..ac230e2126 100644 --- a/test/unit-tests/stores/room-list-v3/RoomListStoreV3-test.ts +++ b/test/unit-tests/stores/room-list-v3/RoomListStoreV3-test.ts @@ -28,6 +28,7 @@ import SettingsStore from "../../../../src/settings/SettingsStore"; import * as utils from "../../../../src/utils/notifications"; import { Action } from "../../../../src/dispatcher/actions"; import { SettingLevel } from "../../../../src/settings/SettingLevel.ts"; +import { DefaultTagID } from "../../../../src/utils/room/tagRoom.ts"; describe("RoomListStoreV3", () => { async function getRoomListStore() { diff --git a/test/unit-tests/stores/room-list/MessagePreviewStore-test.ts b/test/unit-tests/stores/room-list/MessagePreviewStore-test.ts index c6d1d9d56c..728b17ecb6 100644 --- a/test/unit-tests/stores/room-list/MessagePreviewStore-test.ts +++ b/test/unit-tests/stores/room-list/MessagePreviewStore-test.ts @@ -20,8 +20,8 @@ import { import { MessagePreviewStore } from "../../../../src/stores/room-list/MessagePreviewStore"; import { mkEvent, mkMessage, mkReaction, setupAsyncStoreWithClient, stubClient } from "../../../test-utils"; -import { DefaultTagID } from "../../../../src/stores/room-list/models"; import { mkThread } from "../../../test-utils/threads"; +import { DefaultTagID } from "../../../../src/utils/room/tagRoom.ts"; describe("MessagePreviewStore", () => { let client: Mocked; diff --git a/test/unit-tests/utils/room/tagRoom-test.ts b/test/unit-tests/utils/room/tagRoom-test.ts index a1eb01f7d2..91f9faaabe 100644 --- a/test/unit-tests/utils/room/tagRoom-test.ts +++ b/test/unit-tests/utils/room/tagRoom-test.ts @@ -10,7 +10,7 @@ import { Room } from "matrix-js-sdk/src/matrix"; import RoomListActions from "../../../../src/actions/RoomListActions"; import defaultDispatcher from "../../../../src/dispatcher/dispatcher"; -import { tagRoom } from "../../../../src/utils/room/tagRoom"; +import { DefaultTagID, type TagID, tagRoom } from "../../../../src/utils/room/tagRoom"; import { getMockClientWithEventEmitter } from "../../../test-utils"; describe("tagRoom()", () => {