Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into t3chguy/fix/18089

 Conflicts:
	res/css/structures/_SpaceHierarchy.scss
	src/components/structures/SpaceHierarchy.tsx
	src/i18n/strings/en_EN.json
This commit is contained in:
Michael Telatynski 2021-08-12 11:41:03 +01:00
commit 0a209afdc2
196 changed files with 8563 additions and 2976 deletions

View file

@ -23,6 +23,8 @@ 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
@ -63,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') {

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,
@ -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

@ -56,7 +56,7 @@ class LifecycleStore extends Store<ActionPayload> {
deferredAction: null,
});
break;
case 'syncstate': {
case 'sync_state': {
if (payload.state !== 'PREPARED') {
break;
}

View file

@ -37,9 +37,8 @@ import { EnhancedMap, mapDiff } from "../utils/maps";
import { setHasDiff } from "../utils/sets";
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";
@ -48,6 +47,7 @@ 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;
@ -61,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 IHierarchyRoom {
@ -69,12 +70,10 @@ export interface ISuggestedRoom extends IHierarchyRoom {
const MAX_SUGGESTED_ROOMS = 20;
// All of these settings cause the page to reload and can be costly if read frequently, so read them here only
// 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 spacesTweakAllRoomsEnabled = SettingsStore.getValue("feature_spaces.all_rooms");
const homeSpaceKey = spacesTweakAllRoomsEnabled ? "ALL_ROOMS" : "HOME_SPACE";
const getSpaceContextKey = (space?: Room) => `mx_space_context_${space?.roomId || homeSpaceKey}`;
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) => {
@ -102,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
@ -122,6 +117,13 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
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);
@ -139,13 +141,16 @@ 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 notificationState = this.getNotificationState(space.roomId);
const roomId = notificationState.getFirstRoomWithNotifications();
const roomId = this.getNotificationState(space.roomId).getFirstRoomWithNotifications();
defaultDispatcher.dispatch({
action: "view_room",
room_id: roomId,
@ -200,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",
@ -379,7 +385,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
}
public getSpaceFilteredRoomIds = (space: Room | null): Set<string> => {
if (!space && spacesTweakAllRoomsEnabled) {
if (!space && this.allRoomsInHome) {
return new Set(this.matrixClient.getVisibleRooms().map(r => r.roomId));
}
return this.spaceFilteredRooms.get(space?.roomId || HOME_SPACE) || new Set();
@ -476,7 +482,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
};
private showInHomeSpace = (room: Room) => {
if (spacesTweakAllRoomsEnabled) 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
@ -508,7 +514,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
const oldFilteredRooms = this.spaceFilteredRooms;
this.spaceFilteredRooms = new Map();
if (!spacesTweakAllRoomsEnabled) {
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)));
@ -564,8 +570,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
});
this.spaceFilteredRooms.forEach((roomIds, s) => {
if (this.allRoomsInHome && s === HOME_SPACE) return; // we'll be using the global notification state, skip
// Update NotificationStates
this.getNotificationState(s)?.setRooms(visibleRooms.filter(room => {
this.getNotificationState(s).setRooms(visibleRooms.filter(room => {
if (!roomIds.has(room.roomId)) return false;
if (DMRoomMap.shared().getUserIdForRoomId(room.roomId)) {
@ -665,7 +673,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
// TODO confirm this after implementing parenting behaviour
if (room.isSpaceRoom()) {
this.onSpaceUpdate();
} else if (!spacesTweakAllRoomsEnabled) {
} else if (!this.allRoomsInHome) {
this.onRoomUpdate(room);
}
this.emit(room.roomId);
@ -689,7 +697,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
if (order !== lastOrder) {
this.notifyIfOrderChanged();
}
} else if (ev.getType() === EventType.Tag && !spacesTweakAllRoomsEnabled) {
} 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 || {};
@ -700,7 +708,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();
@ -735,9 +743,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
this.matrixClient.removeListener("Room.myMembership", this.onRoom);
this.matrixClient.removeListener("Room.accountData", this.onRoomAccountData);
this.matrixClient.removeListener("RoomState.events", this.onRoomState);
if (!spacesTweakAllRoomsEnabled) {
this.matrixClient.removeListener("accountData", this.onAccountData);
}
this.matrixClient.removeListener("accountData", this.onAccountData);
}
await this.reset();
}
@ -748,9 +754,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
this.matrixClient.on("Room.myMembership", this.onRoom);
this.matrixClient.on("Room.accountData", this.onRoomAccountData);
this.matrixClient.on("RoomState.events", this.onRoomState);
if (!spacesTweakAllRoomsEnabled) {
this.matrixClient.on("accountData", this.onAccountData);
}
this.matrixClient.on("accountData", this.onAccountData);
this.matrixClient.getCapabilities().then(capabilities => {
this._restrictedJoinRuleSupport = capabilities
@ -781,7 +785,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 (
(!spacesTweakAllRoomsEnabled || this.activeSpace) &&
(!this.allRoomsInHome || this.activeSpace) &&
!this.getSpaceFilteredRoomIds(this.activeSpace).has(roomId)
) {
this.switchToRelatedSpace(roomId);
@ -793,17 +797,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;
}
}
}
@ -874,7 +894,6 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
export default class SpaceStore {
public static spacesEnabled = spacesEnabled;
public static spacesTweakAllRoomsEnabled = spacesTweakAllRoomsEnabled;
private static internalInstance = new 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

@ -36,6 +36,8 @@ import { RoomNotificationStateStore } from "../notifications/RoomNotificationSta
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;
@ -213,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);
@ -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);
@ -718,14 +722,20 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
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,39 +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 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 (!SpaceStore.spacesTweakAllRoomsEnabled) {
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 || !SpaceStore.spacesTweakAllRoomsEnabled) {
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

@ -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 });
}