mirror of
https://github.com/vector-im/element-web.git
synced 2026-05-05 12:16:53 +02:00
Iterate
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
d0235f83d8
commit
39a24dbbe1
@ -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<MatrixClient["setRoomTag"]>[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",
|
||||
() => {
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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";
|
||||
|
||||
|
||||
@ -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 {
|
||||
/**
|
||||
|
||||
@ -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<RoomGeneralContextMenuProps> = ({
|
||||
...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<RoomGeneralContextMenuProps> = ({
|
||||
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}`);
|
||||
}
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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");
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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) {}
|
||||
|
||||
@ -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.
|
||||
|
||||
34
src/stores/room-list/filters/IFilterCondition.ts
Normal file
34
src/stores/room-list/filters/IFilterCondition.ts
Normal file
@ -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;
|
||||
}
|
||||
72
src/stores/room-list/filters/SpaceFilterCondition.ts
Normal file
72
src/stores/room-list/filters/SpaceFilterCondition.ts
Normal file
@ -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<string>();
|
||||
private userIds = new Set<string>();
|
||||
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<void> => {
|
||||
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);
|
||||
}
|
||||
}
|
||||
48
src/stores/room-list/filters/VisibilityProvider.ts
Normal file
48
src/stores/room-list/filters/VisibilityProvider.ts
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
25
src/stores/room-list/previews/IPreview.ts
Normal file
25
src/stores/room-list/previews/IPreview.ts
Normal file
@ -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;
|
||||
}
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
28
src/stores/room-list/previews/LegacyCallHangupEvent.ts
Normal file
28
src/stores/room-list/previews/LegacyCallHangupEvent.ts
Normal file
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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) });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
57
src/stores/room-list/previews/PollStartEventPreview.ts
Normal file
57
src/stores/room-list/previews/PollStartEventPreview.ts
Normal file
@ -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<typeof MatrixClientContext>;
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
48
src/stores/room-list/previews/ReactionEventPreview.ts
Normal file
48
src/stores/room-list/previews/ReactionEventPreview.ts
Normal file
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
27
src/stores/room-list/previews/StickerEventPreview.ts
Normal file
27
src/stores/room-list/previews/StickerEventPreview.ts
Normal file
@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
33
src/stores/room-list/previews/utils.ts
Normal file
33
src/stores/room-list/previews/utils.ts
Normal file
@ -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() ?? "";
|
||||
}
|
||||
@ -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";
|
||||
|
||||
|
||||
@ -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}`);
|
||||
}
|
||||
|
||||
@ -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([]),
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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<MatrixClient>;
|
||||
|
||||
@ -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()", () => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user