Merge branch 'develop' into gsouquet/switch-rooms
This commit is contained in:
commit
c7c6a818f0
89 changed files with 2555 additions and 1232 deletions
|
@ -17,17 +17,18 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
import {Store} from 'flux/utils';
|
||||
import {MatrixError} from "matrix-js-sdk/src/http-api";
|
||||
import { Store } from 'flux/utils';
|
||||
import { MatrixError } from "matrix-js-sdk/src/http-api";
|
||||
|
||||
import dis from '../dispatcher/dispatcher';
|
||||
import {MatrixClientPeg} from '../MatrixClientPeg';
|
||||
import { MatrixClientPeg } from '../MatrixClientPeg';
|
||||
import * as sdk from '../index';
|
||||
import Modal from '../Modal';
|
||||
import { _t } from '../languageHandler';
|
||||
import { getCachedRoomIDForAlias, storeRoomAliasInCache } from '../RoomAliasCache';
|
||||
import {ActionPayload} from "../dispatcher/payloads";
|
||||
import {retry} from "../utils/promise";
|
||||
import { ActionPayload } from "../dispatcher/payloads";
|
||||
import { Action } from "../dispatcher/actions";
|
||||
import { retry } from "../utils/promise";
|
||||
import CountlyAnalytics from "../CountlyAnalytics";
|
||||
|
||||
const NUM_JOIN_RETRY = 5;
|
||||
|
@ -136,13 +137,13 @@ class RoomViewStore extends Store<ActionPayload> {
|
|||
break;
|
||||
// join_room:
|
||||
// - opts: options for joinRoom
|
||||
case 'join_room':
|
||||
case Action.JoinRoom:
|
||||
this.joinRoom(payload);
|
||||
break;
|
||||
case 'join_room_error':
|
||||
case Action.JoinRoomError:
|
||||
this.joinRoomError(payload);
|
||||
break;
|
||||
case 'join_room_ready':
|
||||
case Action.JoinRoomReady:
|
||||
this.setState({ shouldPeek: false });
|
||||
break;
|
||||
case 'on_client_not_viable':
|
||||
|
@ -217,7 +218,11 @@ class RoomViewStore extends Store<ActionPayload> {
|
|||
this.setState(newState);
|
||||
|
||||
if (payload.auto_join) {
|
||||
this.joinRoom(payload);
|
||||
dis.dispatch({
|
||||
...payload,
|
||||
action: Action.JoinRoom,
|
||||
roomId: payload.room_id,
|
||||
});
|
||||
}
|
||||
} else if (payload.room_alias) {
|
||||
// Try the room alias to room ID navigation cache first to avoid
|
||||
|
@ -298,41 +303,16 @@ class RoomViewStore extends Store<ActionPayload> {
|
|||
// We do *not* clear the 'joining' flag because the Room object and/or our 'joined' member event may not
|
||||
// have come down the sync stream yet, and that's the point at which we'd consider the user joined to the
|
||||
// room.
|
||||
dis.dispatch({ action: 'join_room_ready' });
|
||||
dis.dispatch({
|
||||
action: Action.JoinRoomReady,
|
||||
roomId: this.state.roomId,
|
||||
});
|
||||
} catch (err) {
|
||||
dis.dispatch({
|
||||
action: 'join_room_error',
|
||||
action: Action.JoinRoomError,
|
||||
roomId: this.state.roomId,
|
||||
err: err,
|
||||
});
|
||||
|
||||
let msg = err.message ? err.message : JSON.stringify(err);
|
||||
console.log("Failed to join room:", msg);
|
||||
|
||||
if (err.name === "ConnectionError") {
|
||||
msg = _t("There was an error joining the room");
|
||||
} else if (err.errcode === 'M_INCOMPATIBLE_ROOM_VERSION') {
|
||||
msg = <div>
|
||||
{_t("Sorry, your homeserver is too old to participate in this room.")}<br />
|
||||
{_t("Please contact your homeserver administrator.")}
|
||||
</div>;
|
||||
} else if (err.httpStatus === 404) {
|
||||
const invitingUserId = this.getInvitingUserId(this.state.roomId);
|
||||
// only provide a better error message for invites
|
||||
if (invitingUserId) {
|
||||
// if the inviting user is on the same HS, there can only be one cause: they left.
|
||||
if (invitingUserId.endsWith(`:${MatrixClientPeg.get().getDomain()}`)) {
|
||||
msg = _t("The person who invited you already left the room.");
|
||||
} else {
|
||||
msg = _t("The person who invited you already left the room, or their server is offline.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Failed to join room', '', ErrorDialog, {
|
||||
title: _t("Failed to join room"),
|
||||
description: msg,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -351,6 +331,35 @@ class RoomViewStore extends Store<ActionPayload> {
|
|||
joining: false,
|
||||
joinError: payload.err,
|
||||
});
|
||||
const err = payload.err;
|
||||
let msg = err.message ? err.message : JSON.stringify(err);
|
||||
console.log("Failed to join room:", msg);
|
||||
|
||||
if (err.name === "ConnectionError") {
|
||||
msg = _t("There was an error joining the room");
|
||||
} else if (err.errcode === 'M_INCOMPATIBLE_ROOM_VERSION') {
|
||||
msg = <div>
|
||||
{_t("Sorry, your homeserver is too old to participate in this room.")}<br />
|
||||
{_t("Please contact your homeserver administrator.")}
|
||||
</div>;
|
||||
} else if (err.httpStatus === 404) {
|
||||
const invitingUserId = this.getInvitingUserId(this.state.roomId);
|
||||
// only provide a better error message for invites
|
||||
if (invitingUserId) {
|
||||
// if the inviting user is on the same HS, there can only be one cause: they left.
|
||||
if (invitingUserId.endsWith(`:${MatrixClientPeg.get().getDomain()}`)) {
|
||||
msg = _t("The person who invited you already left the room.");
|
||||
} else {
|
||||
msg = _t("The person who invited you already left the room, or their server is offline.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Failed to join room', '', ErrorDialog, {
|
||||
title: _t("Failed to join room"),
|
||||
description: msg,
|
||||
});
|
||||
}
|
||||
|
||||
public reset() {
|
||||
|
|
|
@ -45,6 +45,10 @@ export const UPDATE_INVITED_SPACES = Symbol("invited-spaces");
|
|||
export const UPDATE_SELECTED_SPACE = Symbol("selected-space");
|
||||
// Space Room ID will be emitted when a Space's children change
|
||||
|
||||
export interface ISuggestedRoom extends ISpaceSummaryRoom {
|
||||
viaServers: string[];
|
||||
}
|
||||
|
||||
const MAX_SUGGESTED_ROOMS = 20;
|
||||
|
||||
const getSpaceContextKey = (space?: Room) => `mx_space_context_${space?.roomId || "ALL_ROOMS"}`;
|
||||
|
@ -89,7 +93,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
private spaceFilteredRooms = new Map<string, Set<string>>();
|
||||
// The space currently selected in the Space Panel - if null then All Rooms is selected
|
||||
private _activeSpace?: Room = null;
|
||||
private _suggestedRooms: ISpaceSummaryRoom[] = [];
|
||||
private _suggestedRooms: ISuggestedRoom[] = [];
|
||||
private _invitedSpaces = new Set<Room>();
|
||||
|
||||
public get invitedSpaces(): Room[] {
|
||||
|
@ -104,7 +108,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
return this._activeSpace || null;
|
||||
}
|
||||
|
||||
public get suggestedRooms(): ISpaceSummaryRoom[] {
|
||||
public get suggestedRooms(): ISuggestedRoom[] {
|
||||
return this._suggestedRooms;
|
||||
}
|
||||
|
||||
|
@ -158,31 +162,41 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
}
|
||||
|
||||
if (space) {
|
||||
const data = await this.fetchSuggestedRooms(space);
|
||||
const suggestedRooms = await this.fetchSuggestedRooms(space);
|
||||
if (this._activeSpace === space) {
|
||||
this._suggestedRooms = data.rooms.filter(roomInfo => {
|
||||
return roomInfo.room_type !== RoomType.Space
|
||||
&& this.matrixClient.getRoom(roomInfo.room_id)?.getMyMembership() !== "join";
|
||||
});
|
||||
this._suggestedRooms = suggestedRooms;
|
||||
this.emit(SUGGESTED_ROOMS, this._suggestedRooms);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fetchSuggestedRooms = async (space: Room, limit = MAX_SUGGESTED_ROOMS) => {
|
||||
public fetchSuggestedRooms = async (space: Room, limit = MAX_SUGGESTED_ROOMS): Promise<ISuggestedRoom[]> => {
|
||||
try {
|
||||
const data: {
|
||||
rooms: ISpaceSummaryRoom[];
|
||||
events: ISpaceSummaryEvent[];
|
||||
} = await this.matrixClient.getSpaceSummary(space.roomId, 0, true, false, limit);
|
||||
return data;
|
||||
|
||||
const viaMap = new EnhancedMap<string, Set<string>>();
|
||||
data.events.forEach(ev => {
|
||||
if (ev.type === EventType.SpaceChild && ev.content.via?.length) {
|
||||
ev.content.via.forEach(via => {
|
||||
viaMap.getOrCreate(ev.state_key, new Set()).add(via);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return data.rooms.filter(roomInfo => {
|
||||
return roomInfo.room_type !== RoomType.Space
|
||||
&& this.matrixClient.getRoom(roomInfo.room_id)?.getMyMembership() !== "join";
|
||||
}).map(roomInfo => ({
|
||||
...roomInfo,
|
||||
viaServers: Array.from(viaMap.get(roomInfo.room_id) || []),
|
||||
}));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
return {
|
||||
rooms: [],
|
||||
events: [],
|
||||
};
|
||||
return [];
|
||||
};
|
||||
|
||||
public addRoomToSpace(space: Room, roomId: string, via: string[], suggested = false, autoJoin = false) {
|
||||
|
@ -222,7 +236,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
return room?.currentState.getStateEvents(EventType.SpaceParent)
|
||||
.filter(ev => {
|
||||
const content = ev.getContent();
|
||||
if (!content?.via) return false;
|
||||
if (!content?.via?.length) return false;
|
||||
// TODO apply permissions check to verify that the parent mapping is valid
|
||||
if (canonicalOnly && !content?.canonical) return false;
|
||||
return true;
|
||||
|
@ -361,7 +375,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
const space = this.matrixClient?.getRoom(spaceId);
|
||||
|
||||
// Add relevant DMs
|
||||
space?.getJoinedMembers().forEach(member => {
|
||||
space?.getMembers().forEach(member => {
|
||||
if (member.membership !== "join" && member.membership !== "invite") return;
|
||||
DMRoomMap.shared().getDMRoomsForUserId(member.userId).forEach(roomId => {
|
||||
roomIds.add(roomId);
|
||||
});
|
||||
|
|
73
src/stores/UIStore.ts
Normal file
73
src/stores/UIStore.ts
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import EventEmitter from "events";
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
import ResizeObserverEntry from 'resize-observer-polyfill/src/ResizeObserverEntry';
|
||||
|
||||
export enum UI_EVENTS {
|
||||
Resize = "resize"
|
||||
}
|
||||
|
||||
export type ResizeObserverCallbackFunction = (entries: ResizeObserverEntry[]) => void;
|
||||
|
||||
|
||||
export default class UIStore extends EventEmitter {
|
||||
private static _instance: UIStore = null;
|
||||
|
||||
private resizeObserver: ResizeObserver;
|
||||
|
||||
public windowWidth: number;
|
||||
public windowHeight: number;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// eslint-disable-next-line no-restricted-properties
|
||||
this.windowWidth = window.innerWidth;
|
||||
// eslint-disable-next-line no-restricted-properties
|
||||
this.windowHeight = window.innerHeight;
|
||||
|
||||
this.resizeObserver = new ResizeObserver(this.resizeObserverCallback);
|
||||
this.resizeObserver.observe(document.body);
|
||||
}
|
||||
|
||||
public static get instance(): UIStore {
|
||||
if (!UIStore._instance) {
|
||||
UIStore._instance = new UIStore();
|
||||
}
|
||||
return UIStore._instance;
|
||||
}
|
||||
|
||||
public static destroy(): void {
|
||||
if (UIStore._instance) {
|
||||
UIStore._instance.resizeObserver.disconnect();
|
||||
UIStore._instance.removeAllListeners();
|
||||
UIStore._instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
private resizeObserverCallback = (entries: ResizeObserverEntry[]) => {
|
||||
const { width, height } = entries
|
||||
.find(entry => entry.target === document.body)
|
||||
.contentRect;
|
||||
|
||||
this.windowWidth = width;
|
||||
this.windowHeight = height;
|
||||
|
||||
this.emit(UI_EVENTS.Resize, entries);
|
||||
}
|
||||
}
|
|
@ -94,10 +94,10 @@ export class MessagePreviewStore extends AsyncStoreWithClient<IState> {
|
|||
* @param inTagId The tag ID in which the room resides
|
||||
* @returns The preview, or null if none present.
|
||||
*/
|
||||
public getPreviewForRoom(room: Room, inTagId: TagID): string {
|
||||
public async getPreviewForRoom(room: Room, inTagId: TagID): Promise<string> {
|
||||
if (!room) return null; // invalid room, just return nothing
|
||||
|
||||
if (!this.previews.has(room.roomId)) this.generatePreview(room, inTagId);
|
||||
if (!this.previews.has(room.roomId)) await this.generatePreview(room, inTagId);
|
||||
|
||||
const previews = this.previews.get(room.roomId);
|
||||
if (!previews) return null;
|
||||
|
@ -108,7 +108,7 @@ export class MessagePreviewStore extends AsyncStoreWithClient<IState> {
|
|||
return previews.get(inTagId);
|
||||
}
|
||||
|
||||
private generatePreview(room: Room, tagId?: TagID) {
|
||||
private async generatePreview(room: Room, tagId?: TagID) {
|
||||
const events = room.timeline;
|
||||
if (!events) return; // should only happen in tests
|
||||
|
||||
|
@ -130,6 +130,9 @@ export class MessagePreviewStore extends AsyncStoreWithClient<IState> {
|
|||
}
|
||||
|
||||
const event = events[i];
|
||||
|
||||
await this.matrixClient.decryptEventIfNeeded(event);
|
||||
|
||||
const previewDef = PREVIEWS[event.getType()];
|
||||
if (!previewDef) continue;
|
||||
if (previewDef.isState && isNullOrUndefined(event.getStateKey())) continue;
|
||||
|
@ -173,8 +176,8 @@ export class MessagePreviewStore extends AsyncStoreWithClient<IState> {
|
|||
|
||||
if (payload.action === 'MatrixActions.Room.timeline' || payload.action === 'MatrixActions.Event.decrypted') {
|
||||
const event = payload.event; // TODO: Type out the dispatcher
|
||||
if (!this.previews.has(event.getRoomId())) return; // not important
|
||||
this.generatePreview(this.matrixClient.getRoom(event.getRoomId()), TAG_ANY);
|
||||
if (!this.previews.has(event.getRoomId()) || !event.isLiveEvent) return; // not important
|
||||
await this.generatePreview(this.matrixClient.getRoom(event.getRoomId()), TAG_ANY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,6 @@ import { OrderingAlgorithm } from "./list-ordering/OrderingAlgorithm";
|
|||
import { getListAlgorithmInstance } from "./list-ordering";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { VisibilityProvider } from "../filters/VisibilityProvider";
|
||||
import { MultiLock } from "../../../utils/MultiLock";
|
||||
|
||||
/**
|
||||
* Fired when the Algorithm has determined a list has been updated.
|
||||
|
@ -78,7 +77,6 @@ export class Algorithm extends EventEmitter {
|
|||
} = {};
|
||||
private allowedByFilter: Map<IFilterCondition, Room[]> = new Map<IFilterCondition, Room[]>();
|
||||
private allowedRoomsByFilters: Set<Room> = new Set<Room>();
|
||||
private handlerLock = new MultiLock();
|
||||
|
||||
/**
|
||||
* Set to true to suspend emissions of algorithm updates.
|
||||
|
@ -681,204 +679,191 @@ export class Algorithm extends EventEmitter {
|
|||
public async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<boolean> {
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`Acquiring lock for ${room.roomId} with cause ${cause}`);
|
||||
console.log(`Handle room update for ${room.roomId} called with cause ${cause}`);
|
||||
}
|
||||
const release = await this.handlerLock.acquire(room.roomId);
|
||||
try {
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`Handle room update for ${room.roomId} called with cause ${cause}`);
|
||||
if (!this.algorithms) throw new Error("Not ready: no algorithms to determine tags from");
|
||||
|
||||
// Note: check the isSticky against the room ID just in case the reference is wrong
|
||||
const isSticky = this._stickyRoom && this._stickyRoom.room && this._stickyRoom.room.roomId === room.roomId;
|
||||
if (cause === RoomUpdateCause.NewRoom) {
|
||||
const isForLastSticky = this._lastStickyRoom && this._lastStickyRoom.room === room;
|
||||
const roomTags = this.roomIdsToTags[room.roomId];
|
||||
const hasTags = roomTags && roomTags.length > 0;
|
||||
|
||||
// Don't change the cause if the last sticky room is being re-added. If we fail to
|
||||
// pass the cause through as NewRoom, we'll fail to lie to the algorithm and thus
|
||||
// lose the room.
|
||||
if (hasTags && !isForLastSticky) {
|
||||
console.warn(`${room.roomId} is reportedly new but is already known - assuming TagChange instead`);
|
||||
cause = RoomUpdateCause.PossibleTagChange;
|
||||
}
|
||||
if (!this.algorithms) throw new Error("Not ready: no algorithms to determine tags from");
|
||||
|
||||
// Note: check the isSticky against the room ID just in case the reference is wrong
|
||||
const isSticky = this._stickyRoom && this._stickyRoom.room && this._stickyRoom.room.roomId === room.roomId;
|
||||
if (cause === RoomUpdateCause.NewRoom) {
|
||||
const isForLastSticky = this._lastStickyRoom && this._lastStickyRoom.room === room;
|
||||
const roomTags = this.roomIdsToTags[room.roomId];
|
||||
const hasTags = roomTags && roomTags.length > 0;
|
||||
|
||||
// Don't change the cause if the last sticky room is being re-added. If we fail to
|
||||
// pass the cause through as NewRoom, we'll fail to lie to the algorithm and thus
|
||||
// lose the room.
|
||||
if (hasTags && !isForLastSticky) {
|
||||
console.warn(`${room.roomId} is reportedly new but is already known - assuming TagChange instead`);
|
||||
cause = RoomUpdateCause.PossibleTagChange;
|
||||
}
|
||||
|
||||
// Check to see if the room is known first
|
||||
let knownRoomRef = this.rooms.includes(room);
|
||||
if (hasTags && !knownRoomRef) {
|
||||
console.warn(`${room.roomId} might be a reference change - attempting to update reference`);
|
||||
this.rooms = this.rooms.map(r => r.roomId === room.roomId ? room : r);
|
||||
knownRoomRef = this.rooms.includes(room);
|
||||
if (!knownRoomRef) {
|
||||
console.warn(`${room.roomId} is still not referenced. It may be sticky.`);
|
||||
}
|
||||
}
|
||||
|
||||
// If we have tags for a room and don't have the room referenced, something went horribly
|
||||
// wrong - the reference should have been updated above.
|
||||
if (hasTags && !knownRoomRef && !isSticky) {
|
||||
throw new Error(`${room.roomId} is missing from room array but is known`);
|
||||
}
|
||||
|
||||
// Like above, update the reference to the sticky room if we need to
|
||||
if (hasTags && isSticky) {
|
||||
// Go directly in and set the sticky room's new reference, being careful not
|
||||
// to trigger a sticky room update ourselves.
|
||||
this._stickyRoom.room = room;
|
||||
}
|
||||
|
||||
// If after all that we're still a NewRoom update, add the room if applicable.
|
||||
// We don't do this for the sticky room (because it causes duplication issues)
|
||||
// or if we know about the reference (as it should be replaced).
|
||||
if (cause === RoomUpdateCause.NewRoom && !isSticky && !knownRoomRef) {
|
||||
this.rooms.push(room);
|
||||
// Check to see if the room is known first
|
||||
let knownRoomRef = this.rooms.includes(room);
|
||||
if (hasTags && !knownRoomRef) {
|
||||
console.warn(`${room.roomId} might be a reference change - attempting to update reference`);
|
||||
this.rooms = this.rooms.map(r => r.roomId === room.roomId ? room : r);
|
||||
knownRoomRef = this.rooms.includes(room);
|
||||
if (!knownRoomRef) {
|
||||
console.warn(`${room.roomId} is still not referenced. It may be sticky.`);
|
||||
}
|
||||
}
|
||||
|
||||
let didTagChange = false;
|
||||
if (cause === RoomUpdateCause.PossibleTagChange) {
|
||||
const oldTags = this.roomIdsToTags[room.roomId] || [];
|
||||
const newTags = this.getTagsForRoom(room);
|
||||
const diff = arrayDiff(oldTags, newTags);
|
||||
if (diff.removed.length > 0 || diff.added.length > 0) {
|
||||
for (const rmTag of diff.removed) {
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`Removing ${room.roomId} from ${rmTag}`);
|
||||
}
|
||||
const algorithm: OrderingAlgorithm = this.algorithms[rmTag];
|
||||
if (!algorithm) throw new Error(`No algorithm for ${rmTag}`);
|
||||
await algorithm.handleRoomUpdate(room, RoomUpdateCause.RoomRemoved);
|
||||
this._cachedRooms[rmTag] = algorithm.orderedRooms;
|
||||
this.recalculateFilteredRoomsForTag(rmTag); // update filter to re-sort the list
|
||||
this.recalculateStickyRoom(rmTag); // update sticky room to make sure it moves if needed
|
||||
}
|
||||
for (const addTag of diff.added) {
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`Adding ${room.roomId} to ${addTag}`);
|
||||
}
|
||||
const algorithm: OrderingAlgorithm = this.algorithms[addTag];
|
||||
if (!algorithm) throw new Error(`No algorithm for ${addTag}`);
|
||||
await algorithm.handleRoomUpdate(room, RoomUpdateCause.NewRoom);
|
||||
this._cachedRooms[addTag] = algorithm.orderedRooms;
|
||||
}
|
||||
// If we have tags for a room and don't have the room referenced, something went horribly
|
||||
// wrong - the reference should have been updated above.
|
||||
if (hasTags && !knownRoomRef && !isSticky) {
|
||||
throw new Error(`${room.roomId} is missing from room array but is known - trying to find duplicate`);
|
||||
}
|
||||
|
||||
// Update the tag map so we don't regen it in a moment
|
||||
this.roomIdsToTags[room.roomId] = newTags;
|
||||
// Like above, update the reference to the sticky room if we need to
|
||||
if (hasTags && isSticky) {
|
||||
// Go directly in and set the sticky room's new reference, being careful not
|
||||
// to trigger a sticky room update ourselves.
|
||||
this._stickyRoom.room = room;
|
||||
}
|
||||
|
||||
// If after all that we're still a NewRoom update, add the room if applicable.
|
||||
// We don't do this for the sticky room (because it causes duplication issues)
|
||||
// or if we know about the reference (as it should be replaced).
|
||||
if (cause === RoomUpdateCause.NewRoom && !isSticky && !knownRoomRef) {
|
||||
this.rooms.push(room);
|
||||
}
|
||||
}
|
||||
|
||||
let didTagChange = false;
|
||||
if (cause === RoomUpdateCause.PossibleTagChange) {
|
||||
const oldTags = this.roomIdsToTags[room.roomId] || [];
|
||||
const newTags = this.getTagsForRoom(room);
|
||||
const diff = arrayDiff(oldTags, newTags);
|
||||
if (diff.removed.length > 0 || diff.added.length > 0) {
|
||||
for (const rmTag of diff.removed) {
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`Changing update cause for ${room.roomId} to Timeline to sort rooms`);
|
||||
console.log(`Removing ${room.roomId} from ${rmTag}`);
|
||||
}
|
||||
cause = RoomUpdateCause.Timeline;
|
||||
didTagChange = true;
|
||||
const algorithm: OrderingAlgorithm = this.algorithms[rmTag];
|
||||
if (!algorithm) throw new Error(`No algorithm for ${rmTag}`);
|
||||
await algorithm.handleRoomUpdate(room, RoomUpdateCause.RoomRemoved);
|
||||
this._cachedRooms[rmTag] = algorithm.orderedRooms;
|
||||
this.recalculateFilteredRoomsForTag(rmTag); // update filter to re-sort the list
|
||||
this.recalculateStickyRoom(rmTag); // update sticky room to make sure it moves if needed
|
||||
}
|
||||
for (const addTag of diff.added) {
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`Adding ${room.roomId} to ${addTag}`);
|
||||
}
|
||||
const algorithm: OrderingAlgorithm = this.algorithms[addTag];
|
||||
if (!algorithm) throw new Error(`No algorithm for ${addTag}`);
|
||||
await algorithm.handleRoomUpdate(room, RoomUpdateCause.NewRoom);
|
||||
this._cachedRooms[addTag] = algorithm.orderedRooms;
|
||||
}
|
||||
|
||||
// Update the tag map so we don't regen it in a moment
|
||||
this.roomIdsToTags[room.roomId] = newTags;
|
||||
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`Changing update cause for ${room.roomId} to Timeline to sort rooms`);
|
||||
}
|
||||
cause = RoomUpdateCause.Timeline;
|
||||
didTagChange = true;
|
||||
} else {
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`Received no-op update for ${room.roomId} - changing to Timeline update`);
|
||||
}
|
||||
cause = RoomUpdateCause.Timeline;
|
||||
}
|
||||
|
||||
if (didTagChange && isSticky) {
|
||||
// Manually update the tag for the sticky room without triggering a sticky room
|
||||
// update. The update will be handled implicitly by the sticky room handling and
|
||||
// requires no changes on our part, if we're in the middle of a sticky room change.
|
||||
if (this._lastStickyRoom) {
|
||||
this._stickyRoom = {
|
||||
room,
|
||||
tag: this.roomIdsToTags[room.roomId][0],
|
||||
position: 0, // right at the top as it changed tags
|
||||
};
|
||||
} else {
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`Received no-op update for ${room.roomId} - changing to Timeline update`);
|
||||
}
|
||||
cause = RoomUpdateCause.Timeline;
|
||||
}
|
||||
|
||||
if (didTagChange && isSticky) {
|
||||
// Manually update the tag for the sticky room without triggering a sticky room
|
||||
// update. The update will be handled implicitly by the sticky room handling and
|
||||
// requires no changes on our part, if we're in the middle of a sticky room change.
|
||||
if (this._lastStickyRoom) {
|
||||
this._stickyRoom = {
|
||||
room,
|
||||
tag: this.roomIdsToTags[room.roomId][0],
|
||||
position: 0, // right at the top as it changed tags
|
||||
};
|
||||
} else {
|
||||
// We have to clear the lock as the sticky room change will trigger updates.
|
||||
await this.setStickyRoom(room);
|
||||
}
|
||||
// We have to clear the lock as the sticky room change will trigger updates.
|
||||
await this.setStickyRoom(room);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the update is for a room change which might be the sticky room, prevent it. We
|
||||
// need to make sure that the causes (NewRoom and RoomRemoved) are still triggered though
|
||||
// as the sticky room relies on this.
|
||||
if (cause !== RoomUpdateCause.NewRoom && cause !== RoomUpdateCause.RoomRemoved) {
|
||||
if (this.stickyRoom === room) {
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.warn(
|
||||
`[RoomListDebug] Received ${cause} update for sticky room ${room.roomId} - ignoring`,
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.roomIdsToTags[room.roomId]) {
|
||||
if (CAUSES_REQUIRING_ROOM.includes(cause)) {
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.warn(`Skipping tag update for ${room.roomId} because we don't know about the room`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the update is for a room change which might be the sticky room, prevent it. We
|
||||
// need to make sure that the causes (NewRoom and RoomRemoved) are still triggered though
|
||||
// as the sticky room relies on this.
|
||||
if (cause !== RoomUpdateCause.NewRoom && cause !== RoomUpdateCause.RoomRemoved) {
|
||||
if (this.stickyRoom === room) {
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`[RoomListDebug] Updating tags for room ${room.roomId} (${room.name})`);
|
||||
console.warn(`[RoomListDebug] Received ${cause} update for sticky room ${room.roomId} - ignoring`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the tags for the room and populate the cache
|
||||
const roomTags = this.getTagsForRoom(room).filter(t => !isNullOrUndefined(this.cachedRooms[t]));
|
||||
|
||||
// "This should never happen" condition - we specify DefaultTagID.Untagged in getTagsForRoom(),
|
||||
// which means we should *always* have a tag to go off of.
|
||||
if (!roomTags.length) throw new Error(`Tags cannot be determined for ${room.roomId}`);
|
||||
|
||||
this.roomIdsToTags[room.roomId] = roomTags;
|
||||
|
||||
if (!this.roomIdsToTags[room.roomId]) {
|
||||
if (CAUSES_REQUIRING_ROOM.includes(cause)) {
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`[RoomListDebug] Updated tags for ${room.roomId}:`, roomTags);
|
||||
console.warn(`Skipping tag update for ${room.roomId} because we don't know about the room`);
|
||||
}
|
||||
}
|
||||
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`[RoomListDebug] Reached algorithmic handling for ${room.roomId} and cause ${cause}`);
|
||||
}
|
||||
|
||||
const tags = this.roomIdsToTags[room.roomId];
|
||||
if (!tags) {
|
||||
console.warn(`No tags known for "${room.name}" (${room.roomId})`);
|
||||
return false;
|
||||
}
|
||||
|
||||
let changed = didTagChange;
|
||||
for (const tag of tags) {
|
||||
const algorithm: OrderingAlgorithm = this.algorithms[tag];
|
||||
if (!algorithm) throw new Error(`No algorithm for ${tag}`);
|
||||
|
||||
await algorithm.handleRoomUpdate(room, cause);
|
||||
this._cachedRooms[tag] = algorithm.orderedRooms;
|
||||
|
||||
// Flag that we've done something
|
||||
this.recalculateFilteredRoomsForTag(tag); // update filter to re-sort the list
|
||||
this.recalculateStickyRoom(tag); // update sticky room to make sure it appears if needed
|
||||
changed = true;
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`[RoomListDebug] Updating tags for room ${room.roomId} (${room.name})`);
|
||||
}
|
||||
|
||||
// Get the tags for the room and populate the cache
|
||||
const roomTags = this.getTagsForRoom(room).filter(t => !isNullOrUndefined(this.cachedRooms[t]));
|
||||
|
||||
// "This should never happen" condition - we specify DefaultTagID.Untagged in getTagsForRoom(),
|
||||
// which means we should *always* have a tag to go off of.
|
||||
if (!roomTags.length) throw new Error(`Tags cannot be determined for ${room.roomId}`);
|
||||
|
||||
this.roomIdsToTags[room.roomId] = roomTags;
|
||||
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(
|
||||
`[RoomListDebug] Finished handling ${room.roomId} with cause ${cause} (changed=${changed})`,
|
||||
);
|
||||
console.log(`[RoomListDebug] Updated tags for ${room.roomId}:`, roomTags);
|
||||
}
|
||||
return changed;
|
||||
} finally {
|
||||
release();
|
||||
}
|
||||
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`[RoomListDebug] Reached algorithmic handling for ${room.roomId} and cause ${cause}`);
|
||||
}
|
||||
|
||||
const tags = this.roomIdsToTags[room.roomId];
|
||||
if (!tags) {
|
||||
console.warn(`No tags known for "${room.name}" (${room.roomId})`);
|
||||
return false;
|
||||
}
|
||||
|
||||
let changed = didTagChange;
|
||||
for (const tag of tags) {
|
||||
const algorithm: OrderingAlgorithm = this.algorithms[tag];
|
||||
if (!algorithm) throw new Error(`No algorithm for ${tag}`);
|
||||
|
||||
await algorithm.handleRoomUpdate(room, cause);
|
||||
this._cachedRooms[tag] = algorithm.orderedRooms;
|
||||
|
||||
// Flag that we've done something
|
||||
this.recalculateFilteredRoomsForTag(tag); // update filter to re-sort the list
|
||||
this.recalculateStickyRoom(tag); // update sticky room to make sure it appears if needed
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (SettingsStore.getValue("advancedRoomListLogging")) {
|
||||
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
|
||||
console.log(`[RoomListDebug] Finished handling ${room.roomId} with cause ${cause} (changed=${changed})`);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { FILTER_CHANGED, FilterKind, IFilterCondition } from "./IFilterCondition";
|
||||
import { EventEmitter } from "events";
|
||||
import { removeHiddenChars } from "matrix-js-sdk/src/utils";
|
||||
import { normalize } from "matrix-js-sdk/src/utils";
|
||||
import { throttle } from "lodash";
|
||||
|
||||
/**
|
||||
|
@ -62,20 +62,10 @@ export class NameFilterCondition extends EventEmitter implements IFilterConditio
|
|||
|
||||
if (!room.name) return false; // should realistically not happen: the js-sdk always calculates a name
|
||||
|
||||
return this.matches(room.name);
|
||||
return this.matches(room.normalizedName);
|
||||
}
|
||||
|
||||
private normalize(val: string): string {
|
||||
// Note: we have to match the filter with the removeHiddenChars() room name because the
|
||||
// function strips spaces and other characters (M becomes RN for example, in lowercase).
|
||||
return removeHiddenChars(val.toLowerCase())
|
||||
// Strip all punctuation
|
||||
.replace(/[\\'!"#$%&()*+,\-./:;<=>?@[\]^_`{|}~\u2000-\u206f\u2e00-\u2e7f]/g, "")
|
||||
// We also doubly convert to lowercase to work around oddities of the library.
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
public matches(val: string): boolean {
|
||||
return this.normalize(val).includes(this.normalize(this.search));
|
||||
public matches(normalizedName: string): boolean {
|
||||
return normalizedName.includes(normalize(this.search));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue