Merge remote-tracking branch 'origin/develop' into gsouquet/fix-backdrop-filter

* origin/develop: (1278 commits)
  Add a little padding
  Keep number field in focus when pressing dialpad buttons (#6520)
  Remove old version
  Fix video call persisting when widget removed
  Update link to matrix-js-sdk CONTRIBUTING file (#6557)
  $toast-bg-color -> $system
  $system-... -> $system
  Iterate PR based on feedback
  Remove unnecessary code
  Use AccessibleTooltipButton
  Just upload the PR object itself
  Edit PR Description instead of commenting
  publish the right directory
  Fix Netflify builds from fork PRs
  This doesn't need to be here as it was moved into CallViewButtons
  Make scrollbar dot transparent
  Iterate PR based on feedback
  Don't set hidden RRs labs setting at account level
  Add a comment for weirdly placed div
  Add full class names to animations.scss
  ...
This commit is contained in:
Dariusz Niemczyk 2021-08-13 15:12:07 +02:00
commit 5f9b55eaa9
No known key found for this signature in database
GPG key ID: 28DFE7164F497CB6
690 changed files with 26605 additions and 12563 deletions

View file

@ -22,6 +22,9 @@ import defaultDispatcher from "../dispatcher/dispatcher";
import { arrayHasDiff } from "../utils/arrays";
import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
import { SettingLevel } from "../settings/SettingLevel";
import SpaceStore from "./SpaceStore";
import { Action } from "../dispatcher/actions";
import { SettingUpdatedPayload } from "../dispatcher/payloads/SettingUpdatedPayload";
const MAX_ROOMS = 20; // arbitrary
const AUTOJOIN_WAIT_THRESHOLD_MS = 90000; // 90s, the time we wait for an autojoined room to show up
@ -62,10 +65,11 @@ export class BreadcrumbsStore extends AsyncStoreWithClient<IState> {
protected async onAction(payload: ActionPayload) {
if (!this.matrixClient) return;
if (payload.action === 'setting_updated') {
if (payload.settingName === 'breadcrumb_rooms') {
if (payload.action === Action.SettingUpdated) {
const settingUpdatedPayload = payload as SettingUpdatedPayload;
if (settingUpdatedPayload.settingName === 'breadcrumb_rooms') {
await this.updateRooms();
} else if (payload.settingName === 'breadcrumbs') {
} else if (settingUpdatedPayload.settingName === 'breadcrumbs') {
await this.updateState({ enabled: SettingsStore.getValue("breadcrumbs", null) });
}
} else if (payload.action === 'view_room') {
@ -122,7 +126,7 @@ export class BreadcrumbsStore extends AsyncStoreWithClient<IState> {
}
private async appendRoom(room: Room) {
if (SettingsStore.getValue("feature_spaces") && room.isSpaceRoom()) return; // hide space rooms
if (SpaceStore.spacesEnabled && room.isSpaceRoom()) return; // hide space rooms
let updated = false;
const rooms = (this.state.rooms || []).slice(); // cheap clone

View file

@ -14,12 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { Store } from 'flux/utils';
import { EventType } from "matrix-js-sdk/src/@types/event";
import dis from '../dispatcher/dispatcher';
import GroupStore from './GroupStore';
import Analytics from '../Analytics';
import * as RoomNotifs from "../RoomNotifs";
import { MatrixClientPeg } from '../MatrixClientPeg';
import SettingsStore from "../settings/SettingsStore";
import { CreateEventField } from "../components/views/dialogs/CreateSpaceFromCommunityDialog";
const INITIAL_STATE = {
orderedTags: null,
@ -49,7 +51,7 @@ class GroupFilterOrderStore extends Store {
this.__emitChange();
}
__onDispatch(payload) {
__onDispatch(payload) { // eslint-disable-line @typescript-eslint/naming-convention
switch (payload.action) {
// Initialise state after initial sync
case 'view_room': {
@ -235,8 +237,12 @@ class GroupFilterOrderStore extends Store {
(t) => (t[0] !== '+' || groupIds.includes(t)) && !removedTags.has(t),
);
const cli = MatrixClientPeg.get();
const migratedCommunities = new Set(cli.getRooms().map(r => {
return r.currentState.getStateEvents(EventType.RoomCreate, "")?.getContent()[CreateEventField];
}).filter(Boolean));
const groupIdsToAdd = groupIds.filter(
(groupId) => !tags.includes(groupId) && !removedTags.has(groupId),
(groupId) => !tags.includes(groupId) && !removedTags.has(groupId) && !migratedCommunities.has(groupId),
);
return tagsToKeep.concat(groupIdsToAdd);

View file

@ -20,11 +20,11 @@ import FlairStore from './FlairStore';
import { MatrixClientPeg } from '../MatrixClientPeg';
import dis from '../dispatcher/dispatcher';
function parseMembersResponse(response) {
export function parseMembersResponse(response) {
return response.chunk.map((apiMember) => groupMemberFromApiObject(apiMember));
}
function parseRoomsResponse(response) {
export function parseRoomsResponse(response) {
return response.chunk.map((apiRoom) => groupRoomFromApiObject(apiRoom));
}

View file

@ -44,7 +44,7 @@ class LifecycleStore extends Store<ActionPayload> {
this.__emitChange();
}
protected __onDispatch(payload: ActionPayload) {
protected __onDispatch(payload: ActionPayload) { // eslint-disable-line @typescript-eslint/naming-convention
switch (payload.action) {
case 'do_after_sync_prepared':
this.setState({
@ -56,7 +56,7 @@ class LifecycleStore extends Store<ActionPayload> {
deferredAction: null,
});
break;
case 'syncstate': {
case 'sync_state': {
if (payload.state !== 'PREPARED') {
break;
}

View file

@ -144,7 +144,7 @@ export default class RightPanelStore extends Store<ActionPayload> {
this.__emitChange();
}
__onDispatch(payload: ActionPayload) {
__onDispatch(payload: ActionPayload) { // eslint-disable-line @typescript-eslint/naming-convention
switch (payload.action) {
case 'view_room':
if (payload.room_id === this.lastRoomId) break; // skip this transition, probably a permalink

View file

@ -96,7 +96,7 @@ class RoomViewStore extends Store<ActionPayload> {
this.__emitChange();
}
__onDispatch(payload) {
__onDispatch(payload) { // eslint-disable-line @typescript-eslint/naming-convention
switch (payload.action) {
// view_room:
// - room_alias: '#somealias:matrix.org'
@ -325,8 +325,8 @@ class RoomViewStore extends Store<ActionPayload> {
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.")}
{ _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);
@ -429,7 +429,7 @@ class RoomViewStore extends Store<ActionPayload> {
}
}
let singletonRoomViewStore = null;
let singletonRoomViewStore: RoomViewStore = null;
if (!singletonRoomViewStore) {
singletonRoomViewStore = new RoomViewStore();
}

View file

@ -14,10 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import { ListIteratee, Many, sortBy, throttle } from "lodash";
import { EventType, RoomType } from "matrix-js-sdk/src/@types/event";
import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { ISpaceSummaryRoom } from "matrix-js-sdk/src/@types/spaces";
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
import { IRoomCapability } from "matrix-js-sdk/src/client";
import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
import defaultDispatcher from "../dispatcher/dispatcher";
@ -31,13 +35,19 @@ import { RoomNotificationStateStore } from "./notifications/RoomNotificationStat
import { DefaultTagID } from "./room-list/models";
import { EnhancedMap, mapDiff } from "../utils/maps";
import { setHasDiff } from "../utils/sets";
import { ISpaceSummaryEvent, ISpaceSummaryRoom } from "../components/structures/SpaceRoomDirectory";
import RoomViewStore from "./RoomViewStore";
import { Action } from "../dispatcher/actions";
import { arrayHasDiff } from "../utils/arrays";
import { arrayHasDiff, arrayHasOrderChange } from "../utils/arrays";
import { objectDiff } from "../utils/objects";
import { arrayHasOrderChange } from "../utils/arrays";
import { reorderLexicographically } from "../utils/stringOrderField";
import { TAG_ORDER } from "../components/views/rooms/RoomList";
import { shouldShowSpaceSettings } from "../utils/space";
import ToastStore from "./ToastStore";
import { _t } from "../languageHandler";
import GenericToast from "../components/views/toasts/GenericToast";
import Modal from "../Modal";
import InfoDialog from "../components/views/dialogs/InfoDialog";
import { SettingUpdatedPayload } from "../dispatcher/payloads/SettingUpdatedPayload";
type SpaceKey = string | symbol;
@ -51,6 +61,7 @@ export const SUGGESTED_ROOMS = Symbol("suggested-rooms");
export const UPDATE_TOP_LEVEL_SPACES = Symbol("top-level-spaces");
export const UPDATE_INVITED_SPACES = Symbol("invited-spaces");
export const UPDATE_SELECTED_SPACE = Symbol("selected-space");
export const UPDATE_HOME_BEHAVIOUR = Symbol("home-behaviour");
// Space Room ID/HOME_SPACE will be emitted when a Space's children change
export interface ISuggestedRoom extends ISpaceSummaryRoom {
@ -59,8 +70,10 @@ export interface ISuggestedRoom extends ISpaceSummaryRoom {
const MAX_SUGGESTED_ROOMS = 20;
const homeSpaceKey = SettingsStore.getValue("feature_spaces.all_rooms") ? "ALL_ROOMS" : "HOME_SPACE";
const getSpaceContextKey = (space?: Room) => `mx_space_context_${space?.roomId || homeSpaceKey}`;
// This setting causes the page to reload and can be costly if read frequently, so read it here only
const spacesEnabled = SettingsStore.getValue("feature_spaces");
const getSpaceContextKey = (space?: Room) => `mx_space_context_${space?.roomId || "HOME_SPACE"}`;
const partitionSpacesAndRooms = (arr: Room[]): [Room[], Room[]] => { // [spaces, rooms]
return arr.reduce((result, room: Room) => {
@ -88,10 +101,6 @@ const getRoomFn: FetchRoomFn = (room: Room) => {
};
export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
constructor() {
super(defaultDispatcher, {});
}
// The spaces representing the roots of the various tree-like hierarchies
private rootSpaces: Room[] = [];
// The list of rooms not present in any currently joined spaces
@ -107,6 +116,14 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
private _suggestedRooms: ISuggestedRoom[] = [];
private _invitedSpaces = new Set<Room>();
private spaceOrderLocalEchoMap = new Map<string, string>();
private _restrictedJoinRuleSupport?: IRoomCapability;
private _allRoomsInHome: boolean = SettingsStore.getValue("Spaces.allRoomsInHome");
constructor() {
super(defaultDispatcher, {});
SettingsStore.monitorSetting("Spaces.allRoomsInHome", null);
}
public get invitedSpaces(): Room[] {
return Array.from(this._invitedSpaces);
@ -124,6 +141,48 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
return this._suggestedRooms;
}
public get allRoomsInHome(): boolean {
return this._allRoomsInHome;
}
public async setActiveRoomInSpace(space: Room | null): Promise<void> {
if (space && !space.isSpaceRoom()) return;
if (space !== this.activeSpace) await this.setActiveSpace(space);
if (space) {
const roomId = this.getNotificationState(space.roomId).getFirstRoomWithNotifications();
defaultDispatcher.dispatch({
action: "view_room",
room_id: roomId,
context_switch: true,
});
} else {
const lists = RoomListStore.instance.unfilteredLists;
for (let i = 0; i < TAG_ORDER.length; i++) {
const t = TAG_ORDER[i];
const listRooms = lists[t];
const unreadRoom = listRooms.find((r: Room) => {
if (this.showInHomeSpace(r)) {
const state = RoomNotificationStateStore.instance.getRoomState(r);
return state.isUnread;
}
});
if (unreadRoom) {
defaultDispatcher.dispatch({
action: "view_room",
room_id: unreadRoom.roomId,
context_switch: true,
});
break;
}
}
}
}
public get restrictedJoinRuleSupport(): IRoomCapability {
return this._restrictedJoinRuleSupport;
}
/**
* Sets the active space, updates room list filters,
* optionally switches the user's room back to where they were when they last viewed that space.
@ -132,7 +191,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
* should not be done when the space switch is done implicitly due to another event like switching room.
*/
public async setActiveSpace(space: Room | null, contextSwitch = true) {
if (space === this.activeSpace || (space && !space?.isSpaceRoom())) return;
if (space === this.activeSpace || (space && !space.isSpaceRoom())) return;
this._activeSpace = space;
this.emit(UPDATE_SELECTED_SPACE, this.activeSpace);
@ -146,7 +205,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
// else if the last viewed room in this space is joined then view that
// else view space home or home depending on what is being clicked on
if (space?.getMyMembership() !== "invite" &&
this.matrixClient?.getRoom(roomId)?.getMyMembership() === "join"
this.matrixClient?.getRoom(roomId)?.getMyMembership() === "join" &&
this.getSpaceFilteredRoomIds(space).has(roomId)
) {
defaultDispatcher.dispatch({
action: "view_room",
@ -173,6 +233,65 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
window.localStorage.removeItem(ACTIVE_SPACE_LS_KEY);
}
// New in Spaces beta toast for Restricted Join Rule
const lsKey = "mx_SpaceBeta_restrictedJoinRuleToastSeen";
if (contextSwitch && space?.getJoinRule() === JoinRule.Invite && shouldShowSpaceSettings(space) &&
space.getJoinedMemberCount() > 1 && !localStorage.getItem(lsKey)
&& this.restrictedJoinRuleSupport?.preferred
) {
const toastKey = "restrictedjoinrule";
ToastStore.sharedInstance().addOrReplaceToast({
key: toastKey,
title: _t("New in the Spaces beta"),
props: {
description: _t("Help people in spaces to find and join private rooms"),
acceptLabel: _t("Learn more"),
onAccept: () => {
localStorage.setItem(lsKey, "true");
ToastStore.sharedInstance().dismissToast(toastKey);
Modal.createTrackedDialog("New in the Spaces beta", "restricted join rule", InfoDialog, {
title: _t("Help space members find private rooms"),
description: <>
<p>{ _t("To help space members find and join a private room, " +
"go to that room's Security & Privacy settings.") }</p>
{ /* Reuses classes from TabbedView for simplicity, non-interactive */ }
<div style={{ width: "190px" }}>
<div className="mx_TabbedView_tabLabel">
<span className="mx_TabbedView_maskedIcon mx_RoomSettingsDialog_settingsIcon" />
<span className="mx_TabbedView_tabLabel_text">{ _t("General") }</span>
</div>
<div className="mx_TabbedView_tabLabel mx_TabbedView_tabLabel_active">
<span className="mx_TabbedView_maskedIcon mx_RoomSettingsDialog_securityIcon" />
<span className="mx_TabbedView_tabLabel_text">{ _t("Security & Privacy") }</span>
</div>
<div className="mx_TabbedView_tabLabel">
<span className="mx_TabbedView_maskedIcon mx_RoomSettingsDialog_rolesIcon" />
<span className="mx_TabbedView_tabLabel_text">{ _t("Roles & Permissions") }</span>
</div>
</div>
<p>{ _t("This makes it easy for rooms to stay private to a space, " +
"while letting people in the space find and join them. " +
"All new rooms in a space will have this option available.") }</p>
</>,
button: _t("OK"),
hasCloseButton: false,
fixedWidth: true,
});
},
rejectLabel: _t("Skip"),
onReject: () => {
localStorage.setItem(lsKey, "true");
ToastStore.sharedInstance().dismissToast(toastKey);
},
},
component: GenericToast,
priority: 35,
});
}
if (space) {
const suggestedRooms = await this.fetchSuggestedRooms(space);
if (this._activeSpace === space) {
@ -184,10 +303,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
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);
const data = await this.matrixClient.getSpaceSummary(space.roomId, 0, true, false, limit);
const viaMap = new EnhancedMap<string, Set<string>>();
data.events.forEach(ev => {
@ -219,7 +335,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
}, roomId);
}
private getChildren(spaceId: string): Room[] {
public getChildren(spaceId: string): Room[] {
const room = this.matrixClient?.getRoom(spaceId);
const childEvents = room?.currentState.getStateEvents(EventType.SpaceChild).filter(ev => ev.getContent()?.via);
return sortBy(childEvents, ev => {
@ -262,8 +378,12 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
return sortBy(parents, r => r.roomId)?.[0] || null;
}
public getKnownParents(roomId: string): Set<string> {
return this.parentMap.get(roomId) || new Set();
}
public getSpaceFilteredRoomIds = (space: Room | null): Set<string> => {
if (!space && SettingsStore.getValue("feature_spaces.all_rooms")) {
if (!space && this.allRoomsInHome) {
return new Set(this.matrixClient.getVisibleRooms().map(r => r.roomId));
}
return this.spaceFilteredRooms.get(space?.roomId || HOME_SPACE) || new Set();
@ -360,7 +480,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
};
private showInHomeSpace = (room: Room) => {
if (SettingsStore.getValue("feature_spaces.all_rooms")) return true;
if (this.allRoomsInHome) return true;
if (room.isSpaceRoom()) return false;
return !this.parentMap.get(room.roomId)?.size // put all orphaned rooms in the Home Space
|| DMRoomMap.shared().getUserIdForRoomId(room.roomId) // put all DMs in the Home Space
@ -392,7 +512,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
const oldFilteredRooms = this.spaceFilteredRooms;
this.spaceFilteredRooms = new Map();
if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
if (!this.allRoomsInHome) {
// put all room invites in the Home Space
const invites = visibleRooms.filter(r => !r.isSpaceRoom() && r.getMyMembership() === "invite");
this.spaceFilteredRooms.set(HOME_SPACE, new Set<string>(invites.map(room => room.roomId)));
@ -419,15 +539,13 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
const roomIds = new Set(childRooms.map(r => r.roomId));
const space = this.matrixClient?.getRoom(spaceId);
if (SettingsStore.getValue("feature_spaces.space_member_dms")) {
// Add relevant DMs
space?.getMembers().forEach(member => {
if (member.membership !== "join" && member.membership !== "invite") return;
DMRoomMap.shared().getDMRoomsForUserId(member.userId).forEach(roomId => {
roomIds.add(roomId);
});
// Add relevant DMs
space?.getMembers().forEach(member => {
if (member.membership !== "join" && member.membership !== "invite") return;
DMRoomMap.shared().getDMRoomsForUserId(member.userId).forEach(roomId => {
roomIds.add(roomId);
});
}
});
const newPath = new Set(parentPath).add(spaceId);
childSpaces.forEach(childSpace => {
@ -450,16 +568,17 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
});
this.spaceFilteredRooms.forEach((roomIds, s) => {
// Update NotificationStates
this.getNotificationState(s)?.setRooms(visibleRooms.filter(room => {
if (roomIds.has(room.roomId)) {
if (s !== HOME_SPACE && SettingsStore.getValue("feature_spaces.space_dm_badges")) return true;
if (this.allRoomsInHome && s === HOME_SPACE) return; // we'll be using the global notification state, skip
return !DMRoomMap.shared().getUserIdForRoomId(room.roomId)
|| RoomListStore.instance.getTagsForRoom(room).includes(DefaultTagID.Favourite);
// Update NotificationStates
this.getNotificationState(s).setRooms(visibleRooms.filter(room => {
if (!roomIds.has(room.roomId)) return false;
if (DMRoomMap.shared().getUserIdForRoomId(room.roomId)) {
return s === HOME_SPACE;
}
return false;
return true;
}));
});
}, 100, { trailing: true, leading: true });
@ -552,7 +671,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
// TODO confirm this after implementing parenting behaviour
if (room.isSpaceRoom()) {
this.onSpaceUpdate();
} else if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
} else if (!this.allRoomsInHome) {
this.onRoomUpdate(room);
}
this.emit(room.roomId);
@ -576,7 +695,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
if (order !== lastOrder) {
this.notifyIfOrderChanged();
}
} else if (ev.getType() === EventType.Tag && !SettingsStore.getValue("feature_spaces.all_rooms")) {
} else if (ev.getType() === EventType.Tag && !this.allRoomsInHome) {
// If the room was in favourites and now isn't or the opposite then update its position in the trees
const oldTags = lastEv?.getContent()?.tags || {};
const newTags = ev.getContent()?.tags || {};
@ -587,7 +706,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
};
private onAccountData = (ev: MatrixEvent, lastEvent: MatrixEvent) => {
if (ev.getType() === EventType.Direct) {
if (!this.allRoomsInHome && ev.getType() === EventType.Direct) {
const lastContent = lastEvent.getContent();
const content = ev.getContent();
@ -616,28 +735,29 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
}
protected async onNotReady() {
if (!SettingsStore.getValue("feature_spaces")) return;
if (!SpaceStore.spacesEnabled) return;
if (this.matrixClient) {
this.matrixClient.removeListener("Room", this.onRoom);
this.matrixClient.removeListener("Room.myMembership", this.onRoom);
this.matrixClient.removeListener("Room.accountData", this.onRoomAccountData);
this.matrixClient.removeListener("RoomState.events", this.onRoomState);
if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
this.matrixClient.removeListener("accountData", this.onAccountData);
}
this.matrixClient.removeListener("accountData", this.onAccountData);
}
await this.reset();
}
protected async onReady() {
if (!SettingsStore.getValue("feature_spaces")) return;
if (!spacesEnabled) return;
this.matrixClient.on("Room", this.onRoom);
this.matrixClient.on("Room.myMembership", this.onRoom);
this.matrixClient.on("Room.accountData", this.onRoomAccountData);
this.matrixClient.on("RoomState.events", this.onRoomState);
if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
this.matrixClient.on("accountData", this.onAccountData);
}
this.matrixClient.on("accountData", this.onAccountData);
this.matrixClient.getCapabilities().then(capabilities => {
this._restrictedJoinRuleSupport = capabilities
?.["m.room_versions"]?.["org.matrix.msc3244.room_capabilities"]?.["restricted"];
});
await this.onSpaceUpdate(); // trigger an initial update
@ -649,7 +769,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
}
protected async onAction(payload: ActionPayload) {
if (!SettingsStore.getValue("feature_spaces")) return;
if (!spacesEnabled) return;
switch (payload.action) {
case "view_room": {
// Don't auto-switch rooms when reacting to a context-switch
@ -663,7 +783,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
// as it will cause you to end up in the wrong room
this.setActiveSpace(room, false);
} else if (
(!SettingsStore.getValue("feature_spaces.all_rooms") || this.activeSpace) &&
(!this.allRoomsInHome || this.activeSpace) &&
!this.getSpaceFilteredRoomIds(this.activeSpace).has(roomId)
) {
this.switchToRelatedSpace(roomId);
@ -675,17 +795,33 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
window.localStorage.setItem(getSpaceContextKey(this.activeSpace), payload.room_id);
break;
}
case "after_leave_room":
if (this._activeSpace && payload.room_id === this._activeSpace.roomId) {
this.setActiveSpace(null, false);
}
break;
case Action.SwitchSpace:
if (payload.num === 0) {
this.setActiveSpace(null);
} else if (this.spacePanelSpaces.length >= payload.num) {
this.setActiveSpace(this.spacePanelSpaces[payload.num - 1]);
}
break;
case Action.SettingUpdated: {
const settingUpdatedPayload = payload as SettingUpdatedPayload;
if (settingUpdatedPayload.settingName === "Spaces.allRoomsInHome") {
const newValue = SettingsStore.getValue("Spaces.allRoomsInHome");
if (this.allRoomsInHome !== newValue) {
this._allRoomsInHome = newValue;
this.emit(UPDATE_HOME_BEHAVIOUR, this.allRoomsInHome);
this.rebuild(); // rebuild everything
}
}
break;
}
}
}
@ -755,6 +891,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
}
export default class SpaceStore {
public static spacesEnabled = spacesEnabled;
private static internalInstance = new SpaceStoreClass();
public static get instance(): SpaceStoreClass {

View file

@ -22,10 +22,11 @@ export interface IToast<C extends ComponentClass> {
key: string;
// higher priority number will be shown on top of lower priority
priority: number;
title: string;
title?: string;
icon?: string;
component: C;
className?: string;
bodyClassName?: string;
props?: Omit<React.ComponentProps<C>, "toastKey">; // toastKey is injected by ToastContainer
}

View file

@ -15,7 +15,11 @@ limitations under the License.
*/
import EventEmitter from "events";
import ResizeObserver from 'resize-observer-polyfill';
// XXX: resize-observer-polyfill has types that now conflict with typescript's
// own DOM types: https://github.com/que-etc/resize-observer-polyfill/issues/80
// Using require here rather than import is a horrenous workaround. We should
// be able to remove the polyfill once Safari 14 is released.
const ResizeObserverPolyfill = require('resize-observer-polyfill'); // eslint-disable-line @typescript-eslint/no-var-requires
import ResizeObserverEntry from 'resize-observer-polyfill/src/ResizeObserverEntry';
export enum UI_EVENTS {
@ -43,7 +47,7 @@ export default class UIStore extends EventEmitter {
// eslint-disable-next-line no-restricted-properties
this.windowHeight = window.innerHeight;
this.resizeObserver = new ResizeObserver(this.resizeObserverCallback);
this.resizeObserver = new ResizeObserverPolyfill(this.resizeObserverCallback);
this.resizeObserver.observe(document.body);
}

View file

@ -17,7 +17,7 @@ limitations under the License.
import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
import defaultDispatcher from "../dispatcher/dispatcher";
import { ActionPayload } from "../dispatcher/payloads";
import { VoiceRecording } from "../voice/VoiceRecording";
import { VoiceRecording } from "../audio/VoiceRecording";
interface IState {
recording?: VoiceRecording;

View file

@ -137,6 +137,20 @@ export default class WidgetStore extends AsyncStoreWithClient<IState> {
if (edited && !this.roomMap.has(room.roomId)) {
this.roomMap.set(room.roomId, roomInfo);
}
// If a persistent widget is active, check to see if it's just been removed.
// If it has, it needs to destroyed otherwise unmounting the node won't kill it
const persistentWidgetId = ActiveWidgetStore.getPersistentWidgetId();
if (persistentWidgetId) {
if (
ActiveWidgetStore.getRoomId(persistentWidgetId) === room.roomId &&
!roomInfo.widgets.some(w => w.id === persistentWidgetId)
) {
console.log(`Persistent widget ${persistentWidgetId} removed from room ${room.roomId}: destroying.`);
ActiveWidgetStore.destroyPersistentWidget(persistentWidgetId);
}
}
this.emit(room.roomId);
}

View file

@ -23,7 +23,7 @@ import { NOTIFICATION_STATE_UPDATE, NotificationState } from "./NotificationStat
import { FetchRoomFn } from "./ListNotificationState";
export class SpaceNotificationState extends NotificationState {
private rooms: Room[] = [];
public rooms: Room[] = []; // exposed only for tests
private states: { [spaceId: string]: RoomNotificationState } = {};
constructor(private spaceId: string | symbol, private getRoomFn: FetchRoomFn) {
@ -53,6 +53,10 @@ export class SpaceNotificationState extends NotificationState {
this.calculateTotalState();
}
public getFirstRoomWithNotifications() {
return this.rooms.find((room) => room.getUnreadNotificationCount() > 0).roomId;
}
public destroy() {
super.destroy();
for (const state of Object.values(this.states)) {

View file

@ -63,7 +63,7 @@ const PREVIEWS = {
const MAX_EVENTS_BACKWARDS = 50;
// type merging ftw
type TAG_ANY = "im.vector.any";
type TAG_ANY = "im.vector.any"; // eslint-disable-line @typescript-eslint/naming-convention
const TAG_ANY: TAG_ANY = "im.vector.any";
interface IState {

View file

@ -35,6 +35,9 @@ import { NameFilterCondition } from "./filters/NameFilterCondition";
import { RoomNotificationStateStore } from "../notifications/RoomNotificationStateStore";
import { VisibilityProvider } from "./filters/VisibilityProvider";
import { SpaceWatcher } from "./SpaceWatcher";
import SpaceStore from "../SpaceStore";
import { Action } from "../../dispatcher/actions";
import { SettingUpdatedPayload } from "../../dispatcher/payloads/SettingUpdatedPayload";
interface IState {
tagsEnabled?: boolean;
@ -73,10 +76,11 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
constructor() {
super(defaultDispatcher);
this.setMaxListeners(20); // CustomRoomTagStore + RoomList + LeftPanel + 8xRoomSubList + spares
}
private setupWatchers() {
if (SettingsStore.getValue("feature_spaces")) {
if (SpaceStore.spacesEnabled) {
this.spaceWatcher = new SpaceWatcher(this);
} else {
this.tagWatcher = new TagWatcher(this);
@ -130,8 +134,8 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
// Update any settings here, as some may have happened before we were logically ready.
console.log("Regenerating room lists: Startup");
await this.readAndCacheSettingsFromStore();
await this.regenerateAllLists({ trigger: false });
await this.handleRVSUpdate({ trigger: false }); // fake an RVS update to adjust sticky room, if needed
this.regenerateAllLists({ trigger: false });
this.handleRVSUpdate({ trigger: false }); // fake an RVS update to adjust sticky room, if needed
this.updateFn.mark(); // we almost certainly want to trigger an update.
this.updateFn.trigger();
@ -148,7 +152,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
await this.updateState({
tagsEnabled,
});
await this.updateAlgorithmInstances();
this.updateAlgorithmInstances();
}
/**
@ -156,23 +160,23 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
* @param trigger Set to false to prevent a list update from being sent. Should only
* be used if the calling code will manually trigger the update.
*/
private async handleRVSUpdate({ trigger = true }) {
private handleRVSUpdate({ trigger = true }) {
if (!this.matrixClient) return; // We assume there won't be RVS updates without a client
const activeRoomId = RoomViewStore.getRoomId();
if (!activeRoomId && this.algorithm.stickyRoom) {
await this.algorithm.setStickyRoom(null);
this.algorithm.setStickyRoom(null);
} else if (activeRoomId) {
const activeRoom = this.matrixClient.getRoom(activeRoomId);
if (!activeRoom) {
console.warn(`${activeRoomId} is current in RVS but missing from client - clearing sticky room`);
await this.algorithm.setStickyRoom(null);
this.algorithm.setStickyRoom(null);
} else if (activeRoom !== this.algorithm.stickyRoom) {
if (SettingsStore.getValue("advancedRoomListLogging")) {
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
console.log(`Changing sticky room to ${activeRoomId}`);
}
await this.algorithm.setStickyRoom(activeRoom);
this.algorithm.setStickyRoom(activeRoom);
}
}
@ -211,10 +215,11 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
const logicallyReady = this.matrixClient && this.initialListsGenerated;
if (!logicallyReady) return;
if (payload.action === 'setting_updated') {
if (this.watchedSettings.includes(payload.settingName)) {
if (payload.action === Action.SettingUpdated) {
const settingUpdatedPayload = payload as SettingUpdatedPayload;
if (this.watchedSettings.includes(settingUpdatedPayload.settingName)) {
// TODO: Remove with https://github.com/vector-im/element-web/issues/14602
if (payload.settingName === "advancedRoomListLogging") {
if (settingUpdatedPayload.settingName === "advancedRoomListLogging") {
// Log when the setting changes so we know when it was turned on in the rageshake
const enabled = SettingsStore.getValue("advancedRoomListLogging");
console.warn("Advanced room list logging is enabled? " + enabled);
@ -224,7 +229,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
console.log("Regenerating room lists: Settings changed");
await this.readAndCacheSettingsFromStore();
await this.regenerateAllLists({ trigger: false }); // regenerate the lists now
this.regenerateAllLists({ trigger: false }); // regenerate the lists now
this.updateFn.trigger();
}
}
@ -366,7 +371,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
console.log(`[RoomListDebug] Clearing sticky room due to room upgrade`);
}
await this.algorithm.setStickyRoom(null);
this.algorithm.setStickyRoom(null);
}
// Note: we hit the algorithm instead of our handleRoomUpdate() function to
@ -375,7 +380,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
console.log(`[RoomListDebug] Removing previous room from room list`);
}
await this.algorithm.handleRoomUpdate(prevRoom, RoomUpdateCause.RoomRemoved);
this.algorithm.handleRoomUpdate(prevRoom, RoomUpdateCause.RoomRemoved);
}
}
@ -431,7 +436,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
return; // don't do anything on new/moved rooms which ought not to be shown
}
const shouldUpdate = await this.algorithm.handleRoomUpdate(room, cause);
const shouldUpdate = this.algorithm.handleRoomUpdate(room, cause);
if (shouldUpdate) {
if (SettingsStore.getValue("advancedRoomListLogging")) {
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
@ -460,13 +465,13 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
// Reset the sticky room before resetting the known rooms so the algorithm
// doesn't freak out.
await this.algorithm.setStickyRoom(null);
await this.algorithm.setKnownRooms(rooms);
this.algorithm.setStickyRoom(null);
this.algorithm.setKnownRooms(rooms);
// Set the sticky room back, if needed, now that we have updated the store.
// This will use relative stickyness to the new room set.
if (stickyIsStillPresent) {
await this.algorithm.setStickyRoom(currentSticky);
this.algorithm.setStickyRoom(currentSticky);
}
// Finally, mark an update and resume updates from the algorithm
@ -475,12 +480,12 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
}
public async setTagSorting(tagId: TagID, sort: SortAlgorithm) {
await this.setAndPersistTagSorting(tagId, sort);
this.setAndPersistTagSorting(tagId, sort);
this.updateFn.trigger();
}
private async setAndPersistTagSorting(tagId: TagID, sort: SortAlgorithm) {
await this.algorithm.setTagSorting(tagId, sort);
private setAndPersistTagSorting(tagId: TagID, sort: SortAlgorithm) {
this.algorithm.setTagSorting(tagId, sort);
// TODO: Per-account? https://github.com/vector-im/element-web/issues/14114
localStorage.setItem(`mx_tagSort_${tagId}`, sort);
}
@ -518,13 +523,13 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
return tagSort;
}
public async setListOrder(tagId: TagID, order: ListAlgorithm) {
await this.setAndPersistListOrder(tagId, order);
public setListOrder(tagId: TagID, order: ListAlgorithm) {
this.setAndPersistListOrder(tagId, order);
this.updateFn.trigger();
}
private async setAndPersistListOrder(tagId: TagID, order: ListAlgorithm) {
await this.algorithm.setListOrdering(tagId, order);
private setAndPersistListOrder(tagId: TagID, order: ListAlgorithm) {
this.algorithm.setListOrdering(tagId, order);
// TODO: Per-account? https://github.com/vector-im/element-web/issues/14114
localStorage.setItem(`mx_listOrder_${tagId}`, order);
}
@ -561,7 +566,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
return listOrder;
}
private async updateAlgorithmInstances() {
private updateAlgorithmInstances() {
// We'll require an update, so mark for one. Marking now also prevents the calls
// to setTagSorting and setListOrder from causing triggers.
this.updateFn.mark();
@ -574,10 +579,10 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
const listOrder = this.calculateListOrder(tag);
if (tagSort !== definedSort) {
await this.setAndPersistTagSorting(tag, tagSort);
this.setAndPersistTagSorting(tag, tagSort);
}
if (listOrder !== definedOrder) {
await this.setAndPersistListOrder(tag, listOrder);
this.setAndPersistListOrder(tag, listOrder);
}
}
}
@ -608,9 +613,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
// if spaces are enabled only consider the prefilter conditions when there are no runtime conditions
// for the search all spaces feature
if (this.prefilterConditions.length > 0
&& (!SettingsStore.getValue("feature_spaces") || !this.filterConditions.length)
) {
if (this.prefilterConditions.length > 0 && (!SpaceStore.spacesEnabled || !this.filterConditions.length)) {
rooms = rooms.filter(r => {
for (const filter of this.prefilterConditions) {
if (!filter.isVisible(r)) {
@ -632,7 +635,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
* @param trigger Set to false to prevent a list update from being sent. Should only
* be used if the calling code will manually trigger the update.
*/
public async regenerateAllLists({ trigger = true }) {
public regenerateAllLists({ trigger = true }) {
console.warn("Regenerating all room lists");
const rooms = this.getPlausibleRooms();
@ -656,8 +659,8 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
RoomListLayoutStore.instance.ensureLayoutExists(tagId);
}
await this.algorithm.populateTags(sorts, orders);
await this.algorithm.setKnownRooms(rooms);
this.algorithm.populateTags(sorts, orders);
this.algorithm.setKnownRooms(rooms);
this.initialListsGenerated = true;
@ -682,7 +685,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
} else {
this.filterConditions.push(filter);
// Runtime filters with spaces disable prefiltering for the search all spaces feature
if (SettingsStore.getValue("feature_spaces")) {
if (SpaceStore.spacesEnabled) {
// this has to be awaited so that `setKnownRooms` is called in time for the `addFilterCondition` below
// this way the runtime filters are only evaluated on one dataset and not both.
await this.recalculatePrefiltering();
@ -708,6 +711,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
}
let promise = Promise.resolve();
let idx = this.filterConditions.indexOf(filter);
let removed = false;
if (idx >= 0) {
this.filterConditions.splice(idx, 1);
@ -715,17 +719,23 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
this.algorithm.removeFilterCondition(filter);
}
// Runtime filters with spaces disable prefiltering for the search all spaces feature
if (SettingsStore.getValue("feature_spaces")) {
if (SpaceStore.spacesEnabled) {
promise = this.recalculatePrefiltering();
}
removed = true;
}
idx = this.prefilterConditions.indexOf(filter);
if (idx >= 0) {
filter.off(FILTER_CHANGED, this.onPrefilterUpdated);
this.prefilterConditions.splice(idx, 1);
promise = this.recalculatePrefiltering();
removed = true;
}
if (removed) {
promise.then(() => this.updateFn.trigger());
}
promise.then(() => this.updateFn.trigger());
}
/**

View file

@ -18,40 +18,47 @@ import { Room } from "matrix-js-sdk/src/models/room";
import { RoomListStoreClass } from "./RoomListStore";
import { SpaceFilterCondition } from "./filters/SpaceFilterCondition";
import SpaceStore, { UPDATE_SELECTED_SPACE } from "../SpaceStore";
import SettingsStore from "../../settings/SettingsStore";
import SpaceStore, { UPDATE_HOME_BEHAVIOUR, UPDATE_SELECTED_SPACE } from "../SpaceStore";
/**
* Watches for changes in spaces to manage the filter on the provided RoomListStore
*/
export class SpaceWatcher {
private filter: SpaceFilterCondition;
private readonly filter = new SpaceFilterCondition();
// we track these separately to the SpaceStore as we need to observe transitions
private activeSpace: Room = SpaceStore.instance.activeSpace;
private allRoomsInHome: boolean = SpaceStore.instance.allRoomsInHome;
constructor(private store: RoomListStoreClass) {
if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
this.filter = new SpaceFilterCondition();
if (!this.allRoomsInHome || this.activeSpace) {
this.updateFilter();
store.addFilter(this.filter);
}
SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdated);
SpaceStore.instance.on(UPDATE_HOME_BEHAVIOUR, this.onHomeBehaviourUpdated);
}
private onSelectedSpaceUpdated = (activeSpace?: Room) => {
this.activeSpace = activeSpace;
private onSelectedSpaceUpdated = (activeSpace?: Room, allRoomsInHome = this.allRoomsInHome) => {
if (activeSpace === this.activeSpace && allRoomsInHome === this.allRoomsInHome) return; // nop
if (this.filter) {
if (activeSpace || !SettingsStore.getValue("feature_spaces.all_rooms")) {
this.updateFilter();
} else {
this.store.removeFilter(this.filter);
this.filter = null;
}
} else if (activeSpace) {
this.filter = new SpaceFilterCondition();
const oldActiveSpace = this.activeSpace;
const oldAllRoomsInHome = this.allRoomsInHome;
this.activeSpace = activeSpace;
this.allRoomsInHome = allRoomsInHome;
if (activeSpace || !allRoomsInHome) {
this.updateFilter();
this.store.addFilter(this.filter);
}
if (oldAllRoomsInHome && !oldActiveSpace) {
this.store.addFilter(this.filter);
} else if (allRoomsInHome && !activeSpace) {
this.store.removeFilter(this.filter);
}
};
private onHomeBehaviourUpdated = (allRoomsInHome: boolean) => {
this.onSelectedSpaceUpdated(this.activeSpace, allRoomsInHome);
};
private updateFilter = () => {

View file

@ -16,8 +16,9 @@ limitations under the License.
import { Room } from "matrix-js-sdk/src/models/room";
import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
import DMRoomMap from "../../../utils/DMRoomMap";
import { EventEmitter } from "events";
import DMRoomMap from "../../../utils/DMRoomMap";
import { arrayDiff, arrayHasDiff } from "../../../utils/arrays";
import { DefaultTagID, RoomUpdateCause, TagID } from "../models";
import {
@ -34,6 +35,7 @@ import { OrderingAlgorithm } from "./list-ordering/OrderingAlgorithm";
import { getListAlgorithmInstance } from "./list-ordering";
import SettingsStore from "../../../settings/SettingsStore";
import { VisibilityProvider } from "../filters/VisibilityProvider";
import SpaceStore from "../../SpaceStore";
/**
* Fired when the Algorithm has determined a list has been updated.
@ -121,8 +123,12 @@ export class Algorithm extends EventEmitter {
* Awaitable version of the sticky room setter.
* @param val The new room to sticky.
*/
public async setStickyRoom(val: Room) {
await this.updateStickyRoom(val);
public setStickyRoom(val: Room) {
try {
this.updateStickyRoom(val);
} catch (e) {
console.warn("Failed to update sticky room", e);
}
}
public getTagSorting(tagId: TagID): SortAlgorithm {
@ -130,13 +136,13 @@ export class Algorithm extends EventEmitter {
return this.sortAlgorithms[tagId];
}
public async setTagSorting(tagId: TagID, sort: SortAlgorithm) {
public setTagSorting(tagId: TagID, sort: SortAlgorithm) {
if (!tagId) throw new Error("Tag ID must be defined");
if (!sort) throw new Error("Algorithm must be defined");
this.sortAlgorithms[tagId] = sort;
const algorithm: OrderingAlgorithm = this.algorithms[tagId];
await algorithm.setSortAlgorithm(sort);
algorithm.setSortAlgorithm(sort);
this._cachedRooms[tagId] = algorithm.orderedRooms;
this.recalculateFilteredRoomsForTag(tagId); // update filter to re-sort the list
this.recalculateStickyRoom(tagId); // update sticky room to make sure it appears if needed
@ -147,7 +153,7 @@ export class Algorithm extends EventEmitter {
return this.listAlgorithms[tagId];
}
public async setListOrdering(tagId: TagID, order: ListAlgorithm) {
public setListOrdering(tagId: TagID, order: ListAlgorithm) {
if (!tagId) throw new Error("Tag ID must be defined");
if (!order) throw new Error("Algorithm must be defined");
this.listAlgorithms[tagId] = order;
@ -155,7 +161,7 @@ export class Algorithm extends EventEmitter {
const algorithm = getListAlgorithmInstance(order, tagId, this.sortAlgorithms[tagId]);
this.algorithms[tagId] = algorithm;
await algorithm.setRooms(this._cachedRooms[tagId]);
algorithm.setRooms(this._cachedRooms[tagId]);
this._cachedRooms[tagId] = algorithm.orderedRooms;
this.recalculateFilteredRoomsForTag(tagId); // update filter to re-sort the list
this.recalculateStickyRoom(tagId); // update sticky room to make sure it appears if needed
@ -182,31 +188,25 @@ export class Algorithm extends EventEmitter {
}
}
private async handleFilterChange() {
await this.recalculateFilteredRooms();
private handleFilterChange() {
this.recalculateFilteredRooms();
// re-emit the update so the list store can fire an off-cycle update if needed
if (this.updatesInhibited) return;
this.emit(FILTER_CHANGED);
}
private async updateStickyRoom(val: Room) {
try {
return await this.doUpdateStickyRoom(val);
} finally {
this._lastStickyRoom = null; // clear to indicate we're done changing
}
private updateStickyRoom(val: Room) {
this.doUpdateStickyRoom(val);
this._lastStickyRoom = null; // clear to indicate we're done changing
}
private async doUpdateStickyRoom(val: Room) {
if (SettingsStore.getValue("feature_spaces") && val?.isSpaceRoom() && val.getMyMembership() !== "invite") {
private doUpdateStickyRoom(val: Room) {
if (SpaceStore.spacesEnabled && val?.isSpaceRoom() && val.getMyMembership() !== "invite") {
// no-op sticky rooms for spaces - they're effectively virtual rooms
val = null;
}
// Note throughout: We need async so we can wait for handleRoomUpdate() to do its thing,
// otherwise we risk duplicating rooms.
if (val && !VisibilityProvider.instance.isRoomVisible(val)) {
val = null; // the room isn't visible - lie to the rest of this function
}
@ -222,7 +222,7 @@ export class Algorithm extends EventEmitter {
this._stickyRoom = null; // clear before we go to update the algorithm
// Lie to the algorithm and re-add the room to the algorithm
await this.handleRoomUpdate(stickyRoom, RoomUpdateCause.NewRoom);
this.handleRoomUpdate(stickyRoom, RoomUpdateCause.NewRoom);
return;
}
return;
@ -268,10 +268,10 @@ export class Algorithm extends EventEmitter {
// referential checks as the references can differ through the lifecycle.
if (lastStickyRoom && lastStickyRoom.room && lastStickyRoom.room.roomId !== val.roomId) {
// Lie to the algorithm and re-add the room to the algorithm
await this.handleRoomUpdate(lastStickyRoom.room, RoomUpdateCause.NewRoom);
this.handleRoomUpdate(lastStickyRoom.room, RoomUpdateCause.NewRoom);
}
// Lie to the algorithm and remove the room from it's field of view
await this.handleRoomUpdate(val, RoomUpdateCause.RoomRemoved);
this.handleRoomUpdate(val, RoomUpdateCause.RoomRemoved);
// Check for tag & position changes while we're here. We also check the room to ensure
// it is still the same room.
@ -461,9 +461,8 @@ export class Algorithm extends EventEmitter {
* them.
* @param {ITagSortingMap} tagSortingMap The tags to generate.
* @param {IListOrderingMap} listOrderingMap The ordering of those tags.
* @returns {Promise<*>} A promise which resolves when complete.
*/
public async populateTags(tagSortingMap: ITagSortingMap, listOrderingMap: IListOrderingMap): Promise<any> {
public populateTags(tagSortingMap: ITagSortingMap, listOrderingMap: IListOrderingMap): void {
if (!tagSortingMap) throw new Error(`Sorting map cannot be null or empty`);
if (!listOrderingMap) throw new Error(`Ordering ma cannot be null or empty`);
if (arrayHasDiff(Object.keys(tagSortingMap), Object.keys(listOrderingMap))) {
@ -512,9 +511,8 @@ export class Algorithm extends EventEmitter {
* Seeds the Algorithm with a set of rooms. The algorithm will discard all
* previously known information and instead use these rooms instead.
* @param {Room[]} rooms The rooms to force the algorithm to use.
* @returns {Promise<*>} A promise which resolves when complete.
*/
public async setKnownRooms(rooms: Room[]): Promise<any> {
public setKnownRooms(rooms: Room[]): void {
if (isNullOrUndefined(rooms)) throw new Error(`Array of rooms cannot be null`);
if (!this.sortAlgorithms) throw new Error(`Cannot set known rooms without a tag sorting map`);
@ -528,7 +526,7 @@ export class Algorithm extends EventEmitter {
// Before we go any further we need to clear (but remember) the sticky room to
// avoid accidentally duplicating it in the list.
const oldStickyRoom = this._stickyRoom;
await this.updateStickyRoom(null);
if (oldStickyRoom) this.updateStickyRoom(null);
this.rooms = rooms;
@ -540,7 +538,7 @@ export class Algorithm extends EventEmitter {
// If we can avoid doing work, do so.
if (!rooms.length) {
await this.generateFreshTags(newTags); // just in case it wants to do something
this.generateFreshTags(newTags); // just in case it wants to do something
this.cachedRooms = newTags;
return;
}
@ -577,7 +575,7 @@ export class Algorithm extends EventEmitter {
}
}
await this.generateFreshTags(newTags);
this.generateFreshTags(newTags);
this.cachedRooms = newTags; // this recalculates the filtered rooms for us
this.updateTagsFromCache();
@ -586,7 +584,7 @@ export class Algorithm extends EventEmitter {
// it was. It's entirely possible that it changed lists though, so if it did then
// we also have to update the position of it.
if (oldStickyRoom && oldStickyRoom.room) {
await this.updateStickyRoom(oldStickyRoom.room);
this.updateStickyRoom(oldStickyRoom.room);
if (this._stickyRoom && this._stickyRoom.room) { // just in case the update doesn't go according to plan
if (this._stickyRoom.tag !== oldStickyRoom.tag) {
// We put the sticky room at the top of the list to treat it as an obvious tag change.
@ -651,16 +649,15 @@ export class Algorithm extends EventEmitter {
* @param {ITagMap} updatedTagMap The tag map which needs populating. Each tag
* will already have the rooms which belong to it - they just need ordering. Must
* be mutated in place.
* @returns {Promise<*>} A promise which resolves when complete.
*/
private async generateFreshTags(updatedTagMap: ITagMap): Promise<any> {
private generateFreshTags(updatedTagMap: ITagMap): void {
if (!this.algorithms) throw new Error("Not ready: no algorithms to determine tags from");
for (const tag of Object.keys(updatedTagMap)) {
const algorithm: OrderingAlgorithm = this.algorithms[tag];
if (!algorithm) throw new Error(`No algorithm for ${tag}`);
await algorithm.setRooms(updatedTagMap[tag]);
algorithm.setRooms(updatedTagMap[tag]);
updatedTagMap[tag] = algorithm.orderedRooms;
}
}
@ -672,11 +669,10 @@ export class Algorithm extends EventEmitter {
* may no-op this request if no changes are required.
* @param {Room} room The room which might have affected sorting.
* @param {RoomUpdateCause} cause The reason for the update being triggered.
* @returns {Promise<boolean>} A promise which resolve to true or false
* depending on whether or not getOrderedRooms() should be called after
* processing.
* @returns {Promise<boolean>} A boolean of whether or not getOrderedRooms()
* should be called after processing.
*/
public async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<boolean> {
public handleRoomUpdate(room: Room, cause: RoomUpdateCause): boolean {
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}`);
@ -684,9 +680,9 @@ export class Algorithm extends EventEmitter {
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;
const isSticky = this._stickyRoom?.room?.roomId === room.roomId;
if (cause === RoomUpdateCause.NewRoom) {
const isForLastSticky = this._lastStickyRoom && this._lastStickyRoom.room === room;
const isForLastSticky = this._lastStickyRoom?.room === room;
const roomTags = this.roomIdsToTags[room.roomId];
const hasTags = roomTags && roomTags.length > 0;
@ -743,7 +739,7 @@ export class Algorithm extends EventEmitter {
}
const algorithm: OrderingAlgorithm = this.algorithms[rmTag];
if (!algorithm) throw new Error(`No algorithm for ${rmTag}`);
await algorithm.handleRoomUpdate(room, RoomUpdateCause.RoomRemoved);
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
@ -755,7 +751,7 @@ export class Algorithm extends EventEmitter {
}
const algorithm: OrderingAlgorithm = this.algorithms[addTag];
if (!algorithm) throw new Error(`No algorithm for ${addTag}`);
await algorithm.handleRoomUpdate(room, RoomUpdateCause.NewRoom);
algorithm.handleRoomUpdate(room, RoomUpdateCause.NewRoom);
this._cachedRooms[addTag] = algorithm.orderedRooms;
}
@ -788,7 +784,7 @@ export class Algorithm extends EventEmitter {
};
} else {
// We have to clear the lock as the sticky room change will trigger updates.
await this.setStickyRoom(room);
this.setStickyRoom(room);
}
}
}
@ -851,7 +847,7 @@ export class Algorithm extends EventEmitter {
const algorithm: OrderingAlgorithm = this.algorithms[tag];
if (!algorithm) throw new Error(`No algorithm for ${tag}`);
await algorithm.handleRoomUpdate(room, cause);
algorithm.handleRoomUpdate(room, cause);
this._cachedRooms[tag] = algorithm.orderedRooms;
// Flag that we've done something

View file

@ -94,15 +94,15 @@ export class ImportanceAlgorithm extends OrderingAlgorithm {
return state.color;
}
public async setRooms(rooms: Room[]): Promise<any> {
public setRooms(rooms: Room[]): void {
if (this.sortingAlgorithm === SortAlgorithm.Manual) {
this.cachedOrderedRooms = await sortRoomsWithAlgorithm(rooms, this.tagId, this.sortingAlgorithm);
this.cachedOrderedRooms = sortRoomsWithAlgorithm(rooms, this.tagId, this.sortingAlgorithm);
} else {
// Every other sorting type affects the categories, not the whole tag.
const categorized = this.categorizeRooms(rooms);
for (const category of Object.keys(categorized)) {
const roomsToOrder = categorized[category];
categorized[category] = await sortRoomsWithAlgorithm(roomsToOrder, this.tagId, this.sortingAlgorithm);
categorized[category] = sortRoomsWithAlgorithm(roomsToOrder, this.tagId, this.sortingAlgorithm);
}
const newlyOrganized: Room[] = [];
@ -118,12 +118,12 @@ export class ImportanceAlgorithm extends OrderingAlgorithm {
}
}
private async handleSplice(room: Room, cause: RoomUpdateCause): Promise<boolean> {
private handleSplice(room: Room, cause: RoomUpdateCause): boolean {
if (cause === RoomUpdateCause.NewRoom) {
const category = this.getRoomCategory(room);
this.alterCategoryPositionBy(category, 1, this.indices);
this.cachedOrderedRooms.splice(this.indices[category], 0, room); // splice in the new room (pre-adjusted)
await this.sortCategory(category);
this.sortCategory(category);
} else if (cause === RoomUpdateCause.RoomRemoved) {
const roomIdx = this.getRoomIndex(room);
if (roomIdx === -1) {
@ -141,55 +141,49 @@ export class ImportanceAlgorithm extends OrderingAlgorithm {
return true;
}
public async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<boolean> {
try {
await this.updateLock.acquireAsync();
if (cause === RoomUpdateCause.NewRoom || cause === RoomUpdateCause.RoomRemoved) {
return this.handleSplice(room, cause);
}
if (cause !== RoomUpdateCause.Timeline && cause !== RoomUpdateCause.ReadReceipt) {
throw new Error(`Unsupported update cause: ${cause}`);
}
const category = this.getRoomCategory(room);
if (this.sortingAlgorithm === SortAlgorithm.Manual) {
return; // Nothing to do here.
}
const roomIdx = this.getRoomIndex(room);
if (roomIdx === -1) {
throw new Error(`Room ${room.roomId} has no index in ${this.tagId}`);
}
// Try to avoid doing array operations if we don't have to: only move rooms within
// the categories if we're jumping categories
const oldCategory = this.getCategoryFromIndices(roomIdx, this.indices);
if (oldCategory !== category) {
// Move the room and update the indices
this.moveRoomIndexes(1, oldCategory, category, this.indices);
this.cachedOrderedRooms.splice(roomIdx, 1); // splice out the old index (fixed position)
this.cachedOrderedRooms.splice(this.indices[category], 0, room); // splice in the new room (pre-adjusted)
// Note: if moveRoomIndexes() is called after the splice then the insert operation
// will happen in the wrong place. Because we would have already adjusted the index
// for the category, we don't need to determine how the room is moving in the list.
// If we instead tried to insert before updating the indices, we'd have to determine
// whether the room was moving later (towards IDLE) or earlier (towards RED) from its
// current position, as it'll affect the category's start index after we remove the
// room from the array.
}
// Sort the category now that we've dumped the room in
await this.sortCategory(category);
return true; // change made
} finally {
await this.updateLock.release();
public handleRoomUpdate(room: Room, cause: RoomUpdateCause): boolean {
if (cause === RoomUpdateCause.NewRoom || cause === RoomUpdateCause.RoomRemoved) {
return this.handleSplice(room, cause);
}
if (cause !== RoomUpdateCause.Timeline && cause !== RoomUpdateCause.ReadReceipt) {
throw new Error(`Unsupported update cause: ${cause}`);
}
const category = this.getRoomCategory(room);
if (this.sortingAlgorithm === SortAlgorithm.Manual) {
return; // Nothing to do here.
}
const roomIdx = this.getRoomIndex(room);
if (roomIdx === -1) {
throw new Error(`Room ${room.roomId} has no index in ${this.tagId}`);
}
// Try to avoid doing array operations if we don't have to: only move rooms within
// the categories if we're jumping categories
const oldCategory = this.getCategoryFromIndices(roomIdx, this.indices);
if (oldCategory !== category) {
// Move the room and update the indices
this.moveRoomIndexes(1, oldCategory, category, this.indices);
this.cachedOrderedRooms.splice(roomIdx, 1); // splice out the old index (fixed position)
this.cachedOrderedRooms.splice(this.indices[category], 0, room); // splice in the new room (pre-adjusted)
// Note: if moveRoomIndexes() is called after the splice then the insert operation
// will happen in the wrong place. Because we would have already adjusted the index
// for the category, we don't need to determine how the room is moving in the list.
// If we instead tried to insert before updating the indices, we'd have to determine
// whether the room was moving later (towards IDLE) or earlier (towards RED) from its
// current position, as it'll affect the category's start index after we remove the
// room from the array.
}
// Sort the category now that we've dumped the room in
this.sortCategory(category);
return true; // change made
}
private async sortCategory(category: NotificationColor) {
private sortCategory(category: NotificationColor) {
// This should be relatively quick because the room is usually inserted at the top of the
// category, and most popular sorting algorithms will deal with trying to keep the active
// room at the top/start of the category. For the few algorithms that will have to move the
@ -201,7 +195,7 @@ export class ImportanceAlgorithm extends OrderingAlgorithm {
const startIdx = this.indices[category];
const numSort = nextCategoryStartIdx - startIdx; // splice() returns up to the max, so MAX_SAFE_INT is fine
const unsortedSlice = this.cachedOrderedRooms.splice(startIdx, numSort);
const sorted = await sortRoomsWithAlgorithm(unsortedSlice, this.tagId, this.sortingAlgorithm);
const sorted = sortRoomsWithAlgorithm(unsortedSlice, this.tagId, this.sortingAlgorithm);
this.cachedOrderedRooms.splice(startIdx, 0, ...sorted);
}

View file

@ -29,42 +29,32 @@ export class NaturalAlgorithm extends OrderingAlgorithm {
super(tagId, initialSortingAlgorithm);
}
public async setRooms(rooms: Room[]): Promise<any> {
this.cachedOrderedRooms = await sortRoomsWithAlgorithm(rooms, this.tagId, this.sortingAlgorithm);
public setRooms(rooms: Room[]): void {
this.cachedOrderedRooms = sortRoomsWithAlgorithm(rooms, this.tagId, this.sortingAlgorithm);
}
public async handleRoomUpdate(room, cause): Promise<boolean> {
try {
await this.updateLock.acquireAsync();
const isSplice = cause === RoomUpdateCause.NewRoom || cause === RoomUpdateCause.RoomRemoved;
const isInPlace = cause === RoomUpdateCause.Timeline || cause === RoomUpdateCause.ReadReceipt;
if (!isSplice && !isInPlace) {
throw new Error(`Unsupported update cause: ${cause}`);
}
if (cause === RoomUpdateCause.NewRoom) {
this.cachedOrderedRooms.push(room);
} else if (cause === RoomUpdateCause.RoomRemoved) {
const idx = this.getRoomIndex(room);
if (idx >= 0) {
this.cachedOrderedRooms.splice(idx, 1);
} else {
console.warn(`Tried to remove unknown room from ${this.tagId}: ${room.roomId}`);
}
}
// TODO: Optimize this to avoid useless operations: https://github.com/vector-im/element-web/issues/14457
// For example, we can skip updates to alphabetic (sometimes) and manually ordered tags
this.cachedOrderedRooms = await sortRoomsWithAlgorithm(
this.cachedOrderedRooms,
this.tagId,
this.sortingAlgorithm,
);
return true;
} finally {
await this.updateLock.release();
public handleRoomUpdate(room, cause): boolean {
const isSplice = cause === RoomUpdateCause.NewRoom || cause === RoomUpdateCause.RoomRemoved;
const isInPlace = cause === RoomUpdateCause.Timeline || cause === RoomUpdateCause.ReadReceipt;
if (!isSplice && !isInPlace) {
throw new Error(`Unsupported update cause: ${cause}`);
}
if (cause === RoomUpdateCause.NewRoom) {
this.cachedOrderedRooms.push(room);
} else if (cause === RoomUpdateCause.RoomRemoved) {
const idx = this.getRoomIndex(room);
if (idx >= 0) {
this.cachedOrderedRooms.splice(idx, 1);
} else {
console.warn(`Tried to remove unknown room from ${this.tagId}: ${room.roomId}`);
}
}
// TODO: Optimize this to avoid useless operations: https://github.com/vector-im/element-web/issues/14457
// For example, we can skip updates to alphabetic (sometimes) and manually ordered tags
this.cachedOrderedRooms = sortRoomsWithAlgorithm(this.cachedOrderedRooms, this.tagId, this.sortingAlgorithm);
return true;
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
import { Room } from "matrix-js-sdk/src/models/room";
import { RoomUpdateCause, TagID } from "../../models";
import { SortAlgorithm } from "../models";
import AwaitLock from "await-lock";
/**
* Represents a list ordering algorithm. Subclasses should populate the
@ -26,7 +25,6 @@ import AwaitLock from "await-lock";
export abstract class OrderingAlgorithm {
protected cachedOrderedRooms: Room[];
protected sortingAlgorithm: SortAlgorithm;
protected readonly updateLock = new AwaitLock();
protected constructor(protected tagId: TagID, initialSortingAlgorithm: SortAlgorithm) {
// noinspection JSIgnoredPromiseFromCall
@ -45,21 +43,20 @@ export abstract class OrderingAlgorithm {
* @param newAlgorithm The new algorithm. Must be defined.
* @returns Resolves when complete.
*/
public async setSortAlgorithm(newAlgorithm: SortAlgorithm) {
public setSortAlgorithm(newAlgorithm: SortAlgorithm) {
if (!newAlgorithm) throw new Error("A sorting algorithm must be defined");
this.sortingAlgorithm = newAlgorithm;
// Force regeneration of the rooms
await this.setRooms(this.orderedRooms);
this.setRooms(this.orderedRooms);
}
/**
* Sets the rooms the algorithm should be handling, implying a reconstruction
* of the ordering.
* @param rooms The rooms to use going forward.
* @returns Resolves when complete.
*/
public abstract setRooms(rooms: Room[]): Promise<any>;
public abstract setRooms(rooms: Room[]): void;
/**
* Handle a room update. The Algorithm will only call this for causes which
@ -69,7 +66,7 @@ export abstract class OrderingAlgorithm {
* @param cause The cause of the update.
* @returns True if the update requires the Algorithm to update the presentation layers.
*/
public abstract handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<boolean>;
public abstract handleRoomUpdate(room: Room, cause: RoomUpdateCause): boolean;
protected getRoomIndex(room: Room): number {
let roomIdx = this.cachedOrderedRooms.indexOf(room);

View file

@ -23,7 +23,7 @@ import { compare } from "../../../../utils/strings";
* Sorts rooms according to the browser's determination of alphabetic.
*/
export class AlphabeticAlgorithm implements IAlgorithm {
public async sortRooms(rooms: Room[], tagId: TagID): Promise<Room[]> {
public sortRooms(rooms: Room[], tagId: TagID): Room[] {
return rooms.sort((a, b) => {
return compare(a.name, b.name);
});

View file

@ -25,7 +25,7 @@ export interface IAlgorithm {
* Sorts the given rooms according to the sorting rules of the algorithm.
* @param {Room[]} rooms The rooms to sort.
* @param {TagID} tagId The tag ID in which the rooms are being sorted.
* @returns {Promise<Room[]>} Resolves to the sorted rooms.
* @returns {Room[]} Returns the sorted rooms.
*/
sortRooms(rooms: Room[], tagId: TagID): Promise<Room[]>;
sortRooms(rooms: Room[], tagId: TagID): Room[];
}

View file

@ -22,7 +22,7 @@ import { IAlgorithm } from "./IAlgorithm";
* Sorts rooms according to the tag's `order` property on the room.
*/
export class ManualAlgorithm implements IAlgorithm {
public async sortRooms(rooms: Room[], tagId: TagID): Promise<Room[]> {
public sortRooms(rooms: Room[], tagId: TagID): Room[] {
const getOrderProp = (r: Room) => r.tags[tagId].order || 0;
return rooms.sort((a, b) => {
return getOrderProp(a) - getOrderProp(b);

View file

@ -97,7 +97,7 @@ export const sortRooms = (rooms: Room[]): Room[] => {
* useful to the user.
*/
export class RecentAlgorithm implements IAlgorithm {
public async sortRooms(rooms: Room[], tagId: TagID): Promise<Room[]> {
public sortRooms(rooms: Room[], tagId: TagID): Room[] {
return sortRooms(rooms);
}
}

View file

@ -46,8 +46,8 @@ export function getSortingAlgorithmInstance(algorithm: SortAlgorithm): IAlgorith
* @param {Room[]} rooms The rooms to sort.
* @param {TagID} tagId The tag in which the sorting is occurring.
* @param {SortAlgorithm} algorithm The algorithm to use for sorting.
* @returns {Promise<Room[]>} Resolves to the sorted rooms.
* @returns {Room[]} Returns the sorted rooms.
*/
export function sortRoomsWithAlgorithm(rooms: Room[], tagId: TagID, algorithm: SortAlgorithm): Promise<Room[]> {
export function sortRoomsWithAlgorithm(rooms: Room[], tagId: TagID, algorithm: SortAlgorithm): Room[] {
return getSortingAlgorithmInstance(algorithm).sortRooms(rooms, tagId);
}

View file

@ -18,7 +18,7 @@ import { Room } from "matrix-js-sdk/src/models/room";
import CallHandler from "../../../CallHandler";
import { RoomListCustomisations } from "../../../customisations/RoomList";
import VoipUserMapper from "../../../VoipUserMapper";
import SettingsStore from "../../../settings/SettingsStore";
import SpaceStore from "../../SpaceStore";
export class VisibilityProvider {
private static internalInstance: VisibilityProvider;
@ -50,7 +50,7 @@ export class VisibilityProvider {
}
// hide space rooms as they'll be shown in the SpacePanel
if (SettingsStore.getValue("feature_spaces") && room.isSpaceRoom()) {
if (SpaceStore.spacesEnabled && room.isSpaceRoom()) {
return false;
}

View file

@ -17,7 +17,7 @@ limitations under the License.
import { IPreview } from "./IPreview";
import { TagID } from "../models";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { _t } from "../../../languageHandler";
import { _t, sanitizeForTranslation } from "../../../languageHandler";
import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils";
import ReplyThread from "../../../components/views/elements/ReplyThread";
import { getHtmlText } from "../../../HtmlUtils";
@ -58,6 +58,8 @@ export class MessageEventPreview implements IPreview {
body = getHtmlText(body);
}
body = sanitizeForTranslation(body);
if (msgtype === 'm.emote') {
return _t("* %(senderName)s %(emote)s", { senderName: getSenderName(event), emote: body });
}

View file

@ -51,9 +51,10 @@ import ThemeWatcher from "../../settings/watchers/ThemeWatcher";
import { getCustomTheme } from "../../theme";
import CountlyAnalytics from "../../CountlyAnalytics";
import { ElementWidgetCapabilities } from "./ElementWidgetCapabilities";
import { MatrixEvent, IEvent } from "matrix-js-sdk/src/models/event";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { ELEMENT_CLIENT_ID } from "../../identifiers";
import { getUserLanguage } from "../../languageHandler";
import { WidgetVariableCustomisations } from "../../customisations/WidgetVariables";
// TODO: Destroy all of this code
@ -191,7 +192,8 @@ export class StopGapWidget extends EventEmitter {
}
private runUrlTemplate(opts = { asPopout: false }): string {
const templated = this.mockWidget.getCompleteUrl({
const fromCustomisation = WidgetVariableCustomisations?.provideVariables?.() ?? {};
const defaults: ITemplateParams = {
widgetRoomId: this.roomId,
currentUserId: MatrixClientPeg.get().getUserId(),
userDisplayName: OwnProfileStore.instance.displayName,
@ -199,7 +201,8 @@ export class StopGapWidget extends EventEmitter {
clientId: ELEMENT_CLIENT_ID,
clientTheme: SettingsStore.getValue("theme"),
clientLanguage: getUserLanguage(),
}, opts?.asPopout);
};
const templated = this.mockWidget.getCompleteUrl(Object.assign(defaults, fromCustomisation), opts?.asPopout);
const parsed = new URL(templated);
@ -363,6 +366,9 @@ export class StopGapWidget extends EventEmitter {
}
public async prepare(): Promise<void> {
// Ensure the variables are ready for us to be rendered before continuing
await (WidgetVariableCustomisations?.isReady?.() ?? Promise.resolve());
if (this.scalarToken) return;
const existingMessaging = WidgetMessagingStore.instance.getMessaging(this.mockWidget);
if (existingMessaging) this.messaging = existingMessaging;
@ -415,7 +421,7 @@ export class StopGapWidget extends EventEmitter {
private feedEvent(ev: MatrixEvent) {
if (!this.messaging) return;
const raw = ev.event as IEvent;
const raw = ev.getEffectiveEvent();
this.messaging.feedEvent(raw).catch(e => {
console.error("Error sending event to widget: ", e);
});

View file

@ -159,12 +159,12 @@ export class StopGapWidgetDriver extends WidgetDriver {
if (results.length >= limit) break;
const ev = events[i];
if (ev.getType() !== eventType) continue;
if (ev.getType() !== eventType || ev.isState()) continue;
if (eventType === EventType.RoomMessage && msgtype && msgtype !== ev.getContent()['msgtype']) continue;
results.push(ev);
}
return results.map(e => e.event);
return results.map(e => e.getEffectiveEvent());
}
public async readStateEvents(eventType: string, stateKey: string | undefined, limit: number): Promise<object[]> {