From b5391f8ec8d01fca748c1771c10e5074c364d5bd Mon Sep 17 00:00:00 2001 From: Mike Pennisi Date: Fri, 3 Jul 2020 17:22:33 -0400 Subject: [PATCH 01/29] "ignore"/"unignore" commands: validate user ID Extend the accepted patterns so that users are alerted about invalid input. These patterns are approximations of the Matrix user ID grammer. Resolves https://github.com/vector-im/riot-web/issues/12743 --- src/SlashCommands.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index f667c47b3c..11c955749d 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -660,7 +660,7 @@ export const Commands = [ if (args) { const cli = MatrixClientPeg.get(); - const matches = args.match(/^(\S+)$/); + const matches = args.match(/^(@[^:]+:\S+)$/); if (matches) { const userId = matches[1]; const ignoredUsers = cli.getIgnoredUsers(); @@ -690,7 +690,7 @@ export const Commands = [ if (args) { const cli = MatrixClientPeg.get(); - const matches = args.match(/^(\S+)$/); + const matches = args.match(/(^@[^:]+:\S+$)/); if (matches) { const userId = matches[1]; const ignoredUsers = cli.getIgnoredUsers(); From 8ef4b1b2e73ec1f40347a009e678ac918321a241 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 7 Jul 2020 13:56:46 -0600 Subject: [PATCH 02/29] Replace labs flag with a real setting --- src/components/structures/LoggedInView.tsx | 3 +-- src/settings/Settings.js | 3 ++- src/stores/RoomListStore.js | 2 +- src/stores/room-list/RoomListStore2.ts | 4 ++-- src/stores/room-list/RoomListStoreTempProxy.ts | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 6354bb8739..e65c2bc606 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -667,8 +667,7 @@ class LoggedInView extends React.Component { disabled={this.props.leftDisabled} /> ); - if (SettingsStore.isFeatureEnabled("feature_new_room_list")) { - // TODO: Supply props like collapsed and disabled to LeftPanel2 + if (SettingsStore.getValue("feature_new_room_list")) { leftPanel = ( { return this._matrixClient; } - // TODO: Remove enabled flag with the old RoomListStore: https://github.com/vector-im/riot-web/issues/14231 + // TODO: Remove enabled flag with the old RoomListStore: https://github.com/vector-im/riot-web/issues/14367 private checkEnabled() { - this.enabled = SettingsStore.isFeatureEnabled("feature_new_room_list"); + this.enabled = SettingsStore.getValue("feature_new_room_list"); if (this.enabled) { console.log("âš¡ new room list store engaged"); } diff --git a/src/stores/room-list/RoomListStoreTempProxy.ts b/src/stores/room-list/RoomListStoreTempProxy.ts index 86aff178ee..9aae159e19 100644 --- a/src/stores/room-list/RoomListStoreTempProxy.ts +++ b/src/stores/room-list/RoomListStoreTempProxy.ts @@ -28,7 +28,7 @@ import { ITagMap } from "./algorithms/models"; */ export class RoomListStoreTempProxy { public static isUsingNewStore(): boolean { - return SettingsStore.isFeatureEnabled("feature_new_room_list"); + return SettingsStore.getValue("feature_new_room_list"); } public static addListener(handler: () => void): RoomListStoreTempToken { From a59a8b76a985cc9e7d03fa9ed6f9655365f3e58c Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 7 Jul 2020 14:51:43 -0600 Subject: [PATCH 03/29] Update TODO comments to point to new issue --- res/css/structures/_LeftPanel2.scss | 2 +- res/css/views/rooms/_RoomBreadcrumbs2.scss | 2 +- res/css/views/rooms/_RoomSublist2.scss | 2 +- res/css/views/rooms/_RoomTile2.scss | 2 +- src/components/structures/LeftPanel2.tsx | 4 ++-- src/components/structures/RoomSearch.tsx | 2 +- src/components/views/rooms/RoomBreadcrumbs2.tsx | 4 ++-- src/components/views/rooms/RoomList2.tsx | 4 ++-- src/components/views/rooms/RoomSublist2.tsx | 4 ++-- src/components/views/rooms/RoomTile2.tsx | 4 ++-- .../views/settings/tabs/user/PreferencesUserSettingsTab.js | 4 ++-- src/stores/BreadcrumbsStore.ts | 6 +++--- src/stores/room-list/MessagePreviewStore.ts | 2 +- src/stores/room-list/RoomListStore2.ts | 4 ++-- src/stores/room-list/RoomListStoreTempProxy.ts | 2 +- 15 files changed, 24 insertions(+), 24 deletions(-) diff --git a/res/css/structures/_LeftPanel2.scss b/res/css/structures/_LeftPanel2.scss index a73658d916..10eb9dd2e9 100644 --- a/res/css/structures/_LeftPanel2.scss +++ b/res/css/structures/_LeftPanel2.scss @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231 +// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367 $tagPanelWidth: 70px; // only applies in this file, used for calculations diff --git a/res/css/views/rooms/_RoomBreadcrumbs2.scss b/res/css/views/rooms/_RoomBreadcrumbs2.scss index 6e5a5fbb16..0c3c41622e 100644 --- a/res/css/views/rooms/_RoomBreadcrumbs2.scss +++ b/res/css/views/rooms/_RoomBreadcrumbs2.scss @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231 +// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367 .mx_RoomBreadcrumbs2 { width: 100%; diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss index 0e76152f86..9c919023e6 100644 --- a/res/css/views/rooms/_RoomSublist2.scss +++ b/res/css/views/rooms/_RoomSublist2.scss @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231 +// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367 .mx_RoomSublist2 { // The sublist is a column of rows, essentially diff --git a/res/css/views/rooms/_RoomTile2.scss b/res/css/views/rooms/_RoomTile2.scss index 50d376a66f..d844c14443 100644 --- a/res/css/views/rooms/_RoomTile2.scss +++ b/res/css/views/rooms/_RoomTile2.scss @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231 +// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367 // Note: the room tile expects to be in a flexbox column container .mx_RoomTile2 { diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index 7fac6cbff1..769f7daefd 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -34,8 +34,8 @@ import RoomListStore, { LISTS_UPDATE_EVENT } from "../../stores/room-list/RoomLi import {Key} from "../../Keyboard"; import IndicatorScrollbar from "../structures/IndicatorScrollbar"; -// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14231 -// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231 +// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14367 +// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367 /******************************************************************* * CAUTION * diff --git a/src/components/structures/RoomSearch.tsx b/src/components/structures/RoomSearch.tsx index 15f3bd5b54..d152a2b030 100644 --- a/src/components/structures/RoomSearch.tsx +++ b/src/components/structures/RoomSearch.tsx @@ -25,7 +25,7 @@ import { Key } from "../../Keyboard"; import AccessibleButton from "../views/elements/AccessibleButton"; import { Action } from "../../dispatcher/actions"; -// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14231 +// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14367 /******************************************************************* * CAUTION * diff --git a/src/components/views/rooms/RoomBreadcrumbs2.tsx b/src/components/views/rooms/RoomBreadcrumbs2.tsx index 687f4dd73e..fce8c6ee3a 100644 --- a/src/components/views/rooms/RoomBreadcrumbs2.tsx +++ b/src/components/views/rooms/RoomBreadcrumbs2.tsx @@ -28,8 +28,8 @@ import RoomListStore from "../../../stores/room-list/RoomListStore2"; import { DefaultTagID } from "../../../stores/room-list/models"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; -// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14231 -// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231 +// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14367 +// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367 /******************************************************************* * CAUTION * diff --git a/src/components/views/rooms/RoomList2.tsx b/src/components/views/rooms/RoomList2.tsx index fb0136fb29..db246b182d 100644 --- a/src/components/views/rooms/RoomList2.tsx +++ b/src/components/views/rooms/RoomList2.tsx @@ -42,8 +42,8 @@ import { TagSpecificNotificationState } from "../../../stores/notifications/TagS import { Action } from "../../../dispatcher/actions"; import { ViewRoomDeltaPayload } from "../../../dispatcher/payloads/ViewRoomDeltaPayload"; -// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14231 -// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231 +// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14367 +// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367 /******************************************************************* * CAUTION * diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index eefd29f0b7..60d4e307a1 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -42,8 +42,8 @@ import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import { Key } from "../../../Keyboard"; import StyledCheckbox from "../elements/StyledCheckbox"; -// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14231 -// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231 +// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14367 +// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367 /******************************************************************* * CAUTION * diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index abb31a6f71..3b15d7b27a 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -51,8 +51,8 @@ import NotificationBadge from "./NotificationBadge"; import { NotificationColor } from "../../../stores/notifications/NotificationColor"; import { Volume } from "../../../RoomNotifsTypes"; -// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14231 -// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231 +// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14367 +// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367 /******************************************************************* * CAUTION * diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index 40b622cf37..abe6b48712 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -32,12 +32,12 @@ export default class PreferencesUserSettingsTab extends React.Component { 'breadcrumbs', ]; - // TODO: Remove temp structures: https://github.com/vector-im/riot-web/issues/14231 + // TODO: Remove temp structures: https://github.com/vector-im/riot-web/issues/14367 static ROOM_LIST_2_SETTINGS = [ 'breadcrumbs', ]; - // TODO: Remove temp structures: https://github.com/vector-im/riot-web/issues/14231 + // TODO: Remove temp structures: https://github.com/vector-im/riot-web/issues/14367 static eligibleRoomListSettings = () => { if (RoomListStoreTempProxy.isUsingNewStore()) { return PreferencesUserSettingsTab.ROOM_LIST_2_SETTINGS; diff --git a/src/stores/BreadcrumbsStore.ts b/src/stores/BreadcrumbsStore.ts index c78f15c3b4..70d4e19a32 100644 --- a/src/stores/BreadcrumbsStore.ts +++ b/src/stores/BreadcrumbsStore.ts @@ -57,7 +57,7 @@ export class BreadcrumbsStore extends AsyncStoreWithClient { protected async onAction(payload: ActionPayload) { if (!this.matrixClient) return; - // TODO: Remove when new room list is made the default: https://github.com/vector-im/riot-web/issues/14231 + // TODO: Remove when new room list is made the default: https://github.com/vector-im/riot-web/issues/14367 if (!RoomListStoreTempProxy.isUsingNewStore()) return; if (payload.action === 'setting_updated') { @@ -80,7 +80,7 @@ export class BreadcrumbsStore extends AsyncStoreWithClient { } protected async onReady() { - // TODO: Remove when new room list is made the default: https://github.com/vector-im/riot-web/issues/14231 + // TODO: Remove when new room list is made the default: https://github.com/vector-im/riot-web/issues/14367 if (!RoomListStoreTempProxy.isUsingNewStore()) return; await this.updateRooms(); @@ -91,7 +91,7 @@ export class BreadcrumbsStore extends AsyncStoreWithClient { } protected async onNotReady() { - // TODO: Remove when new room list is made the default: https://github.com/vector-im/riot-web/issues/14231 + // TODO: Remove when new room list is made the default: https://github.com/vector-im/riot-web/issues/14367 if (!RoomListStoreTempProxy.isUsingNewStore()) return; this.matrixClient.removeListener("Room.myMembership", this.onMyMembership); diff --git a/src/stores/room-list/MessagePreviewStore.ts b/src/stores/room-list/MessagePreviewStore.ts index 01ddde2e17..ea7fa830cd 100644 --- a/src/stores/room-list/MessagePreviewStore.ts +++ b/src/stores/room-list/MessagePreviewStore.ts @@ -192,7 +192,7 @@ export class MessagePreviewStore extends AsyncStoreWithClient { protected async onAction(payload: ActionPayload) { if (!this.matrixClient) return; - // TODO: Remove when new room list is made the default: https://github.com/vector-im/riot-web/issues/14231 + // TODO: Remove when new room list is made the default: https://github.com/vector-im/riot-web/issues/14367 if (!RoomListStoreTempProxy.isUsingNewStore()) return; if (payload.action === 'MatrixActions.Room.timeline' || payload.action === 'MatrixActions.Event.decrypted') { diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index 1e5b4f302f..1acff9d3fd 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -89,7 +89,7 @@ export class RoomListStore2 extends AsyncStore { } private onRVSUpdate = () => { - if (!this.enabled) return; // TODO: Remove with https://github.com/vector-im/riot-web/issues/14231 + if (!this.enabled) return; // TODO: Remove with https://github.com/vector-im/riot-web/issues/14367 if (!this.matrixClient) return; // We assume there won't be RVS updates without a client const activeRoomId = RoomViewStore.getRoomId(); @@ -115,7 +115,7 @@ export class RoomListStore2 extends AsyncStore { return; } - // TODO: Remove with https://github.com/vector-im/riot-web/issues/14231 + // TODO: Remove with https://github.com/vector-im/riot-web/issues/14367 this.checkEnabled(); if (!this.enabled) return; diff --git a/src/stores/room-list/RoomListStoreTempProxy.ts b/src/stores/room-list/RoomListStoreTempProxy.ts index 9aae159e19..2a5348ab6e 100644 --- a/src/stores/room-list/RoomListStoreTempProxy.ts +++ b/src/stores/room-list/RoomListStoreTempProxy.ts @@ -24,7 +24,7 @@ import { ITagMap } from "./algorithms/models"; * Temporary RoomListStore proxy. Should be replaced with RoomListStore2 when * it is available to everyone. * - * TODO: Delete this: https://github.com/vector-im/riot-web/issues/14231 + * TODO: Delete this: https://github.com/vector-im/riot-web/issues/14367 */ export class RoomListStoreTempProxy { public static isUsingNewStore(): boolean { From c774b88bdabd0914109277ebc35c5fe5a18eb182 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 7 Jul 2020 15:06:06 -0600 Subject: [PATCH 04/29] Initial pass of fixing tests * Use new components * Use new tagId prop on sublists * Define onResize for the room list so it doesn't crash --- test/components/views/rooms/RoomList-test.js | 30 ++++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/test/components/views/rooms/RoomList-test.js b/test/components/views/rooms/RoomList-test.js index d0694a8437..681f5ccb6b 100644 --- a/test/components/views/rooms/RoomList-test.js +++ b/test/components/views/rooms/RoomList-test.js @@ -57,11 +57,11 @@ describe('RoomList', () => { parentDiv = document.createElement('div'); document.body.appendChild(parentDiv); - const RoomList = sdk.getComponent('views.rooms.RoomList'); + const RoomList = sdk.getComponent('views.rooms.RoomList2'); const WrappedRoomList = TestUtils.wrapInMatrixClientContext(RoomList); root = ReactDOM.render( - + {}} /> , parentDiv); ReactTestUtils.findRenderedComponentWithType(root, RoomList); @@ -117,8 +117,8 @@ describe('RoomList', () => { }); function expectRoomInSubList(room, subListTest) { - const RoomSubList = sdk.getComponent('structures.RoomSubList'); - const RoomTile = sdk.getComponent('views.rooms.RoomTile'); + const RoomSubList = sdk.getComponent('views.rooms.RoomSublist2'); + const RoomTile = sdk.getComponent('views.rooms.RoomTile2'); const subLists = ReactTestUtils.scryRenderedComponentsWithType(root, RoomSubList); const containingSubList = subLists.find(subListTest); @@ -140,20 +140,20 @@ describe('RoomList', () => { expect(expectedRoomTile.props.room).toBe(room); } - function expectCorrectMove(oldTag, newTag) { - const getTagSubListTest = (tag) => { - if (tag === undefined) return (s) => s.props.label.endsWith('Rooms'); - return (s) => s.props.tagName === tag; + function expectCorrectMove(oldTagId, newTagId) { + const getTagSubListTest = (tagId) => { + return (s) => s.props.tagId === tagId; }; // Default to finding the destination sublist with newTag - const destSubListTest = getTagSubListTest(newTag); - const srcSubListTest = getTagSubListTest(oldTag); + const destSubListTest = getTagSubListTest(newTagId); + const srcSubListTest = getTagSubListTest(oldTagId); // Set up the room that will be moved such that it has the correct state for a room in - // the section for oldTag - if (['m.favourite', 'm.lowpriority'].includes(oldTag)) movingRoom.tags = {[oldTag]: {}}; - if (oldTag === DefaultTagID.DM) { + // the section for oldTagId + if (oldTagId === DefaultTagID.Favourite || oldTagId === DefaultTagID.LowPriority) { + movingRoom.tags = {[oldTagId]: {}}; + } else if (oldTagId === DefaultTagID.DM) { // Mock inverse m.direct DMRoomMap.shared().roomToUser = { [movingRoom.roomId]: '@someotheruser:domain', @@ -167,7 +167,7 @@ describe('RoomList', () => { expectRoomInSubList(movingRoom, srcSubListTest); dis.dispatch({action: 'RoomListActions.tagRoom.pending', request: { - oldTag, newTag, room: movingRoom, + oldTagId, newTagId, room: movingRoom, }}); // Run all setTimeouts for dispatches and room list rate limiting @@ -287,7 +287,7 @@ describe('RoomList', () => { clock.runAll(); // By default, the test will - expectRoomInSubList(otherRoom, (s) => s.props.label.endsWith('Rooms')); + expectRoomInSubList(otherRoom, (s) => s.props.tagId === DefaultTagID.Untagged); }); itDoesCorrectOptimisticUpdatesForDraggedRoomTiles(); From f89fcd1fe9c3df9d44add57f160bab408b3e5ec6 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 7 Jul 2020 15:45:59 -0600 Subject: [PATCH 05/29] Fix tests and add general safety We don't need the fake clock anymore, but we do have to wait for async actions to complete before moving forward. This also exposes a number of functions for the store to be puppetted with. --- src/stores/room-list/RoomListStore2.ts | 43 ++++++++++++++------ test/components/views/rooms/RoomList-test.js | 40 ++++++++++-------- 2 files changed, 55 insertions(+), 28 deletions(-) diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index 1acff9d3fd..63c3abdf09 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -72,6 +72,34 @@ export class RoomListStore2 extends AsyncStore { return this._matrixClient; } + // Intended for test usage + public async resetStore() { + await this.reset(); + this.tagWatcher = new TagWatcher(this); + this.filterConditions = []; + this.initialListsGenerated = false; + this._matrixClient = null; + + this.algorithm.off(LIST_UPDATED_EVENT, this.onAlgorithmListUpdated); + this.algorithm = new Algorithm(); + this.algorithm.on(LIST_UPDATED_EVENT, this.onAlgorithmListUpdated); + } + + // Public for test usage. Do not call this. + public async makeReady(client: MatrixClient) { + // TODO: Remove with https://github.com/vector-im/riot-web/issues/14367 + this.checkEnabled(); + if (!this.enabled) return; + + this._matrixClient = client; + + // 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(); + this.onRVSUpdate(); // fake an RVS update to adjust sticky room, if needed + } + // TODO: Remove enabled flag with the old RoomListStore: https://github.com/vector-im/riot-web/issues/14367 private checkEnabled() { this.enabled = SettingsStore.getValue("feature_new_room_list"); @@ -115,17 +143,7 @@ export class RoomListStore2 extends AsyncStore { return; } - // TODO: Remove with https://github.com/vector-im/riot-web/issues/14367 - this.checkEnabled(); - if (!this.enabled) return; - - this._matrixClient = payload.matrixClient; - - // 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(); - this.onRVSUpdate(); // fake an RVS update to adjust sticky room, if needed + await this.makeReady(payload.matrixClient); } // TODO: Remove this once the RoomListStore becomes default @@ -372,7 +390,8 @@ export class RoomListStore2 extends AsyncStore { this.emit(LISTS_UPDATE_EVENT, this); }; - private async regenerateAllLists() { + // This is only exposed externally for the tests. Do not call this within the app. + public async regenerateAllLists() { console.warn("Regenerating all room lists"); const sorts: ITagSortingMap = {}; diff --git a/test/components/views/rooms/RoomList-test.js b/test/components/views/rooms/RoomList-test.js index 681f5ccb6b..ba53676fc1 100644 --- a/test/components/views/rooms/RoomList-test.js +++ b/test/components/views/rooms/RoomList-test.js @@ -15,11 +15,17 @@ import GroupStore from '../../../../src/stores/GroupStore.js'; import { MatrixClient, Room, RoomMember } from 'matrix-js-sdk'; import {DefaultTagID} from "../../../../src/stores/room-list/models"; +import RoomListStore, {LISTS_UPDATE_EVENT} from "../../../../src/stores/room-list/RoomListStore2"; function generateRoomId() { return '!' + Math.random().toString().slice(2, 10) + ':domain'; } +function waitForRoomListStoreUpdate() { + return new Promise((resolve) => { + RoomListStore.instance.once(LISTS_UPDATE_EVENT, () => resolve()); + }); +} describe('RoomList', () => { function createRoom(opts) { @@ -34,7 +40,6 @@ describe('RoomList', () => { let client = null; let root = null; const myUserId = '@me:domain'; - let clock = null; const movingRoomId = '!someroomid'; let movingRoom; @@ -43,15 +48,13 @@ describe('RoomList', () => { let myMember; let myOtherMember; - beforeEach(function() { + beforeEach(async function(done) { TestUtils.stubClient(); client = MatrixClientPeg.get(); client.credentials = {userId: myUserId}; //revert this to prototype method as the test-utils monkey-patches this to return a hardcoded value client.getUserId = MatrixClient.prototype.getUserId; - clock = lolex.install(); - DMRoomMap.makeShared(); parentDiv = document.createElement('div'); @@ -102,16 +105,22 @@ describe('RoomList', () => { }); client.getRoom = (roomId) => roomMap[roomId]; + + // Now that everything has been set up, prepare and update the store + await RoomListStore.instance.makeReady(client); + + done(); }); - afterEach((done) => { + afterEach(async (done) => { if (parentDiv) { ReactDOM.unmountComponentAtNode(parentDiv); parentDiv.remove(); parentDiv = null; } - clock.uninstall(); + await RoomListStore.instance.resetLayouts(); + await RoomListStore.instance.resetStore(); done(); }); @@ -127,6 +136,7 @@ describe('RoomList', () => { try { const roomTiles = ReactTestUtils.scryRenderedComponentsWithType(containingSubList, RoomTile); console.info({roomTiles: roomTiles.length}); + console.log("IS SAME?", room === roomTiles[0].props.room, room, roomTiles[0].props.room); expectedRoomTile = roomTiles.find((tile) => tile.props.room === room); } catch (err) { // truncate the error message because it's spammy @@ -162,17 +172,12 @@ describe('RoomList', () => { dis.dispatch({action: 'MatrixActions.sync', prevState: null, state: 'PREPARED', matrixClient: client}); - clock.runAll(); - expectRoomInSubList(movingRoom, srcSubListTest); dis.dispatch({action: 'RoomListActions.tagRoom.pending', request: { oldTagId, newTagId, room: movingRoom, }}); - // Run all setTimeouts for dispatches and room list rate limiting - clock.runAll(); - expectRoomInSubList(movingRoom, destSubListTest); } @@ -269,6 +274,12 @@ describe('RoomList', () => { }; GroupStore._notifyListeners(); + // We also have to mock the client's getGroup function for the room list to filter it. + // It's not smart enough to tell the difference between a real group and a template though. + client.getGroup = (groupId) => { + return {groupId}; + }; + // Select tag dis.dispatch({action: 'select_tag', tag: '+group:domain'}, true); } @@ -277,16 +288,13 @@ describe('RoomList', () => { setupSelectedTag(); }); - it('displays the correct rooms when the groups rooms are changed', () => { + it('displays the correct rooms when the groups rooms are changed', async () => { GroupStore.getGroupRooms = (groupId) => { return [movingRoom, otherRoom]; }; GroupStore._notifyListeners(); - // Run through RoomList debouncing - clock.runAll(); - - // By default, the test will + await waitForRoomListStoreUpdate(); expectRoomInSubList(otherRoom, (s) => s.props.tagId === DefaultTagID.Untagged); }); From 767db73853f58d116d2007bdaba31338c46c4ed5 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 7 Jul 2020 15:50:04 -0600 Subject: [PATCH 06/29] Appease the linter --- test/components/views/rooms/RoomList-test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/components/views/rooms/RoomList-test.js b/test/components/views/rooms/RoomList-test.js index ba53676fc1..ade74dfe44 100644 --- a/test/components/views/rooms/RoomList-test.js +++ b/test/components/views/rooms/RoomList-test.js @@ -1,7 +1,6 @@ import React from 'react'; import ReactTestUtils from 'react-dom/test-utils'; import ReactDOM from 'react-dom'; -import lolex from 'lolex'; import * as TestUtils from '../../../test-utils'; From 044c2238990dfb6ecd164dcbed89680d7f17bf9b Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 7 Jul 2020 15:53:12 -0600 Subject: [PATCH 07/29] Remove debug --- test/components/views/rooms/RoomList-test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/components/views/rooms/RoomList-test.js b/test/components/views/rooms/RoomList-test.js index ade74dfe44..7876ad0f18 100644 --- a/test/components/views/rooms/RoomList-test.js +++ b/test/components/views/rooms/RoomList-test.js @@ -135,7 +135,6 @@ describe('RoomList', () => { try { const roomTiles = ReactTestUtils.scryRenderedComponentsWithType(containingSubList, RoomTile); console.info({roomTiles: roomTiles.length}); - console.log("IS SAME?", room === roomTiles[0].props.room, room, roomTiles[0].props.room); expectedRoomTile = roomTiles.find((tile) => tile.props.room === room); } catch (err) { // truncate the error message because it's spammy From 85af3ebcc0b3548a4fc3915bb721fd27b0783600 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 7 Jul 2020 15:58:15 -0600 Subject: [PATCH 08/29] Lie about DMs in tests --- test/components/views/rooms/RoomList-test.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/components/views/rooms/RoomList-test.js b/test/components/views/rooms/RoomList-test.js index 7876ad0f18..91e5e77937 100644 --- a/test/components/views/rooms/RoomList-test.js +++ b/test/components/views/rooms/RoomList-test.js @@ -56,6 +56,11 @@ describe('RoomList', () => { DMRoomMap.makeShared(); + // Lie to the room list store about DMs not existing + DMRoomMap.getUserIdForRoomId = () => { + return null; + } + parentDiv = document.createElement('div'); document.body.appendChild(parentDiv); From 5ace405062cf150c9c9672c593bbed78abae688c Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 7 Jul 2020 16:01:42 -0600 Subject: [PATCH 09/29] The linter will never be appeased --- test/components/views/rooms/RoomList-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/components/views/rooms/RoomList-test.js b/test/components/views/rooms/RoomList-test.js index 91e5e77937..3521ec0705 100644 --- a/test/components/views/rooms/RoomList-test.js +++ b/test/components/views/rooms/RoomList-test.js @@ -59,7 +59,7 @@ describe('RoomList', () => { // Lie to the room list store about DMs not existing DMRoomMap.getUserIdForRoomId = () => { return null; - } + }; parentDiv = document.createElement('div'); document.body.appendChild(parentDiv); From 121e41d20b4bc9baa73d7a2ea36f90a1b8b6804f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 7 Jul 2020 16:20:53 -0600 Subject: [PATCH 10/29] Remove irrelevant function --- test/components/views/rooms/RoomList-test.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/components/views/rooms/RoomList-test.js b/test/components/views/rooms/RoomList-test.js index 3521ec0705..7876ad0f18 100644 --- a/test/components/views/rooms/RoomList-test.js +++ b/test/components/views/rooms/RoomList-test.js @@ -56,11 +56,6 @@ describe('RoomList', () => { DMRoomMap.makeShared(); - // Lie to the room list store about DMs not existing - DMRoomMap.getUserIdForRoomId = () => { - return null; - }; - parentDiv = document.createElement('div'); document.body.appendChild(parentDiv); From f12d9512098dbd4e56dc1f985f042a6d79e6ff5f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 7 Jul 2020 16:34:42 -0600 Subject: [PATCH 11/29] Update end-to-end tests for new room list --- .../src/usecases/accept-invite.js | 6 ++-- .../src/usecases/create-room.js | 36 +++++++++---------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/test/end-to-end-tests/src/usecases/accept-invite.js b/test/end-to-end-tests/src/usecases/accept-invite.js index 3f208cc1fc..a61aaec64c 100644 --- a/test/end-to-end-tests/src/usecases/accept-invite.js +++ b/test/end-to-end-tests/src/usecases/accept-invite.js @@ -15,10 +15,12 @@ See the License for the specific language governing permissions and limitations under the License. */ +const {findSublist} = require("./create-room"); + module.exports = async function acceptInvite(session, name) { session.log.step(`accepts "${name}" invite`); - //TODO: brittle selector - const invitesHandles = await session.queryAll('.mx_RoomTile_name.mx_RoomTile_invite'); + const inviteSublist = await findSublist("invites"); + const invitesHandles = await inviteSublist.$(".mx_RoomTile2_name"); const invitesWithText = await Promise.all(invitesHandles.map(async (inviteHandle) => { const text = await session.innerText(inviteHandle); return {inviteHandle, text}; diff --git a/test/end-to-end-tests/src/usecases/create-room.js b/test/end-to-end-tests/src/usecases/create-room.js index 7e219fd159..50cb1e02f3 100644 --- a/test/end-to-end-tests/src/usecases/create-room.js +++ b/test/end-to-end-tests/src/usecases/create-room.js @@ -16,21 +16,27 @@ limitations under the License. */ async function openRoomDirectory(session) { - const roomDirectoryButton = await session.query('.mx_LeftPanel_explore .mx_AccessibleButton'); + const roomDirectoryButton = await session.query('.mx_LeftPanel2_exploreButton'); await roomDirectoryButton.click(); } +async function findSublist(name) { + const sublists = await session.queryAll('.mx_RoomSublist2'); + for (const sublist of sublists) { + const header = await sublist.$('.mx_RoomSublist2_headerText'); + const headerText = await session.innerText(header); + if (headerText.toLowerCase().includes(name.toLowerCase())) { + return sublist; + } + } + throw new Error(`could not find room list section that contains '${name}' in header`); +} + async function createRoom(session, roomName, encrypted=false) { session.log.step(`creates room "${roomName}"`); - const roomListHeaders = await session.queryAll('.mx_RoomSubList_labelContainer'); - const roomListHeaderLabels = await Promise.all(roomListHeaders.map(h => session.innerText(h))); - const roomsIndex = roomListHeaderLabels.findIndex(l => l.toLowerCase().includes("rooms")); - if (roomsIndex === -1) { - throw new Error("could not find room list section that contains 'rooms' in header"); - } - const roomsHeader = roomListHeaders[roomsIndex]; - const addRoomButton = await roomsHeader.$(".mx_RoomSubList_addRoom"); + const roomsSublist = await findSublist("rooms"); + const addRoomButton = await roomsSublist.$(".mx_RoomSublist2_auxButton"); await addRoomButton.click(); const roomNameInput = await session.query('.mx_CreateRoomDialog_name input'); @@ -51,14 +57,8 @@ async function createRoom(session, roomName, encrypted=false) { async function createDm(session, invitees) { session.log.step(`creates DM with ${JSON.stringify(invitees)}`); - const roomListHeaders = await session.queryAll('.mx_RoomSubList_labelContainer'); - const roomListHeaderLabels = await Promise.all(roomListHeaders.map(h => session.innerText(h))); - const dmsIndex = roomListHeaderLabels.findIndex(l => l.toLowerCase().includes('direct messages')); - if (dmsIndex === -1) { - throw new Error("could not find room list section that contains 'direct messages' in header"); - } - const dmsHeader = roomListHeaders[dmsIndex]; - const startChatButton = await dmsHeader.$(".mx_RoomSubList_addRoom"); + const dmsSublist = await findSublist("people"); + const startChatButton = await dmsSublist.$(".mx_RoomSublist2_auxButton"); await startChatButton.click(); const inviteesEditor = await session.query('.mx_InviteDialog_editor textarea'); @@ -83,4 +83,4 @@ async function createDm(session, invitees) { session.log.done(); } -module.exports = {openRoomDirectory, createRoom, createDm}; +module.exports = {openRoomDirectory, findSublist, createRoom, createDm}; From 9000888013f27f75b106719832eeb88f571414dc Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 7 Jul 2020 16:38:24 -0600 Subject: [PATCH 12/29] Pass the session through --- test/end-to-end-tests/src/usecases/accept-invite.js | 2 +- test/end-to-end-tests/src/usecases/create-room.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/end-to-end-tests/src/usecases/accept-invite.js b/test/end-to-end-tests/src/usecases/accept-invite.js index a61aaec64c..27992e16df 100644 --- a/test/end-to-end-tests/src/usecases/accept-invite.js +++ b/test/end-to-end-tests/src/usecases/accept-invite.js @@ -19,7 +19,7 @@ const {findSublist} = require("./create-room"); module.exports = async function acceptInvite(session, name) { session.log.step(`accepts "${name}" invite`); - const inviteSublist = await findSublist("invites"); + const inviteSublist = await findSublist(session, "invites"); const invitesHandles = await inviteSublist.$(".mx_RoomTile2_name"); const invitesWithText = await Promise.all(invitesHandles.map(async (inviteHandle) => { const text = await session.innerText(inviteHandle); diff --git a/test/end-to-end-tests/src/usecases/create-room.js b/test/end-to-end-tests/src/usecases/create-room.js index 50cb1e02f3..24e42b92dd 100644 --- a/test/end-to-end-tests/src/usecases/create-room.js +++ b/test/end-to-end-tests/src/usecases/create-room.js @@ -20,7 +20,7 @@ async function openRoomDirectory(session) { await roomDirectoryButton.click(); } -async function findSublist(name) { +async function findSublist(session, name) { const sublists = await session.queryAll('.mx_RoomSublist2'); for (const sublist of sublists) { const header = await sublist.$('.mx_RoomSublist2_headerText'); @@ -35,7 +35,7 @@ async function findSublist(name) { async function createRoom(session, roomName, encrypted=false) { session.log.step(`creates room "${roomName}"`); - const roomsSublist = await findSublist("rooms"); + const roomsSublist = await findSublist(session, "rooms"); const addRoomButton = await roomsSublist.$(".mx_RoomSublist2_auxButton"); await addRoomButton.click(); @@ -57,7 +57,7 @@ async function createRoom(session, roomName, encrypted=false) { async function createDm(session, invitees) { session.log.step(`creates DM with ${JSON.stringify(invitees)}`); - const dmsSublist = await findSublist("people"); + const dmsSublist = await findSublist(session, "people"); const startChatButton = await dmsSublist.$(".mx_RoomSublist2_auxButton"); await startChatButton.click(); From 9bf2505e51db82afe2ef5d8e8b4356e9f25b79fc Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 7 Jul 2020 16:48:03 -0600 Subject: [PATCH 13/29] queryAll, not just query --- test/end-to-end-tests/src/usecases/accept-invite.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/end-to-end-tests/src/usecases/accept-invite.js b/test/end-to-end-tests/src/usecases/accept-invite.js index 27992e16df..d38fdcd0db 100644 --- a/test/end-to-end-tests/src/usecases/accept-invite.js +++ b/test/end-to-end-tests/src/usecases/accept-invite.js @@ -20,7 +20,7 @@ const {findSublist} = require("./create-room"); module.exports = async function acceptInvite(session, name) { session.log.step(`accepts "${name}" invite`); const inviteSublist = await findSublist(session, "invites"); - const invitesHandles = await inviteSublist.$(".mx_RoomTile2_name"); + const invitesHandles = await inviteSublist.$$(".mx_RoomTile2_name"); const invitesWithText = await Promise.all(invitesHandles.map(async (inviteHandle) => { const text = await session.innerText(inviteHandle); return {inviteHandle, text}; From bba819759215326d417f6ea5d3de8ee1074d06cd Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 9 Jul 2020 22:40:34 -0600 Subject: [PATCH 14/29] Use the new layout store --- test/components/views/rooms/RoomList-test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/components/views/rooms/RoomList-test.js b/test/components/views/rooms/RoomList-test.js index 7876ad0f18..81ccf7d7f8 100644 --- a/test/components/views/rooms/RoomList-test.js +++ b/test/components/views/rooms/RoomList-test.js @@ -15,6 +15,7 @@ import GroupStore from '../../../../src/stores/GroupStore.js'; import { MatrixClient, Room, RoomMember } from 'matrix-js-sdk'; import {DefaultTagID} from "../../../../src/stores/room-list/models"; import RoomListStore, {LISTS_UPDATE_EVENT} from "../../../../src/stores/room-list/RoomListStore2"; +import RoomListLayoutStore from "../../../../src/stores/room-list/RoomListLayoutStore"; function generateRoomId() { return '!' + Math.random().toString().slice(2, 10) + ':domain'; @@ -118,7 +119,7 @@ describe('RoomList', () => { parentDiv = null; } - await RoomListStore.instance.resetLayouts(); + await RoomListLayoutStore.instance.resetLayouts(); await RoomListStore.instance.resetStore(); done(); From f8db0a4637ada26406cfd475d287f61d523381c5 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 10 Jul 2020 10:21:00 -0600 Subject: [PATCH 15/29] Resolve complex merge conflicts --- src/stores/room-list/RoomListStore2.ts | 33 +++++++------------------- 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index bbc2e7f478..d53514dd7a 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -97,11 +97,14 @@ export class RoomListStore2 extends AsyncStore { this._matrixClient = client; + // Update any settings here, as some may have happened before we were logically ready. // 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(); - this.onRVSUpdate(); // fake an RVS update to adjust sticky room, if needed + await this.regenerateAllLists({trigger: false}); + await this.handleRVSUpdate({trigger: false}); // fake an RVS update to adjust sticky room, if needed + + this.updateFn.trigger(); } // TODO: Remove enabled flag with the old RoomListStore: https://github.com/vector-im/riot-web/issues/14367 @@ -162,25 +165,9 @@ export class RoomListStore2 extends AsyncStore { return; } -<<<<<<< await this.makeReady(payload.matrixClient); -======= - // TODO: Remove with https://github.com/vector-im/riot-web/issues/14231 - this.checkEnabled(); - if (!this.enabled) return; - - this._matrixClient = payload.matrixClient; - - // 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.updateFn.trigger(); return; // no point in running the next conditions - they won't match ->>>>>>> } // TODO: Remove this once the RoomListStore becomes default @@ -511,17 +498,15 @@ export class RoomListStore2 extends AsyncStore { this.updateFn.mark(); }; -<<<<<<< - // This is only exposed externally for the tests. Do not call this within the app. - public async regenerateAllLists() { -======= /** * Regenerates the room whole room list, discarding any previous results. + * + * Note: This is only exposed externally for the tests. Do not call this from within + * the app. * @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 regenerateAllLists({trigger = true}) { ->>>>>>> + public async regenerateAllLists({trigger = true}) { console.warn("Regenerating all room lists"); const sorts: ITagSortingMap = {}; From 314250a6e4234aa56c0c967de983000377801f5c Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 10 Jul 2020 10:38:07 -0600 Subject: [PATCH 16/29] Add a test mode flag to the store --- src/stores/room-list/RoomListStore2.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index d53514dd7a..acbb72f1d8 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -46,6 +46,12 @@ interface IState { export const LISTS_UPDATE_EVENT = "lists_update"; export class RoomListStore2 extends AsyncStore { + /** + * Set to true if you're running tests on the store. Should not be touched in + * any other environment. + */ + public static TEST_MODE = false; + private _matrixClient: MatrixClient; private initialListsGenerated = false; private enabled = false; @@ -104,6 +110,7 @@ export class RoomListStore2 extends AsyncStore { await this.regenerateAllLists({trigger: false}); await 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(); } @@ -152,7 +159,14 @@ export class RoomListStore2 extends AsyncStore { if (trigger) this.updateFn.trigger(); } - protected onDispatch(payload: ActionPayload) { + protected async onDispatch(payload: ActionPayload) { + // When we're running tests we can't reliably use setImmediate out of timing concerns. + // As such, we use a more synchronous model. + if (RoomListStore2.TEST_MODE) { + await this.onDispatchAsync(payload); + return; + } + // We do this to intentionally break out of the current event loop task, allowing // us to instead wait for a more convenient time to run our updates. setImmediate(() => this.onDispatchAsync(payload)); From 3826d8135838f494cba4d6c834b279a8b54394b3 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 10 Jul 2020 11:05:56 -0600 Subject: [PATCH 17/29] Enable test mode --- test/components/views/rooms/RoomList-test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/components/views/rooms/RoomList-test.js b/test/components/views/rooms/RoomList-test.js index 81ccf7d7f8..e84f943708 100644 --- a/test/components/views/rooms/RoomList-test.js +++ b/test/components/views/rooms/RoomList-test.js @@ -14,7 +14,7 @@ import GroupStore from '../../../../src/stores/GroupStore.js'; import { MatrixClient, Room, RoomMember } from 'matrix-js-sdk'; import {DefaultTagID} from "../../../../src/stores/room-list/models"; -import RoomListStore, {LISTS_UPDATE_EVENT} from "../../../../src/stores/room-list/RoomListStore2"; +import RoomListStore, {LISTS_UPDATE_EVENT, RoomListStore2} from "../../../../src/stores/room-list/RoomListStore2"; import RoomListLayoutStore from "../../../../src/stores/room-list/RoomListLayoutStore"; function generateRoomId() { @@ -49,6 +49,8 @@ describe('RoomList', () => { let myOtherMember; beforeEach(async function(done) { + RoomListStore2.TEST_MODE = true; + TestUtils.stubClient(); client = MatrixClientPeg.get(); client.credentials = {userId: myUserId}; From bb6d46f92666b302523b3d849ed3e5b7f9f7c61f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 10 Jul 2020 15:57:05 -0600 Subject: [PATCH 18/29] When the algorithm changes, re-add the filter listener --- src/stores/room-list/RoomListStore2.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index 0d1ddfc4ca..75e43b3b57 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -92,8 +92,10 @@ export class RoomListStore2 extends AsyncStore { this._matrixClient = null; this.algorithm.off(LIST_UPDATED_EVENT, this.onAlgorithmListUpdated); + this.algorithm.off(FILTER_CHANGED, this.onAlgorithmListUpdated); this.algorithm = new Algorithm(); this.algorithm.on(LIST_UPDATED_EVENT, this.onAlgorithmListUpdated); + this.algorithm.on(FILTER_CHANGED, this.onAlgorithmListUpdated); } // Public for test usage. Do not call this. From 3062d14a783f31211b0d836eeea0847cce7f69ba Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 10 Jul 2020 16:07:24 -0600 Subject: [PATCH 19/29] Convert ImportanceAlgorithm over to using NotificationColor instead Fixes https://github.com/vector-im/riot-web/issues/14362 implicitly By re-using constructs we already have, we don't need to invent code which figures it out. --- .../list-ordering/ImportanceAlgorithm.ts | 78 ++++++------------- 1 file changed, 23 insertions(+), 55 deletions(-) diff --git a/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts b/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts index 88789d3a50..b3f1c2b146 100644 --- a/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts +++ b/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts @@ -19,47 +19,29 @@ import { Room } from "matrix-js-sdk/src/models/room"; import { RoomUpdateCause, TagID } from "../../models"; import { SortAlgorithm } from "../models"; import { sortRoomsWithAlgorithm } from "../tag-sorting"; -import * as Unread from '../../../../Unread'; import { OrderingAlgorithm } from "./OrderingAlgorithm"; - -/** - * The determined category of a room. - */ -export enum Category { - /** - * The room has unread mentions within. - */ - Red = "RED", - /** - * The room has unread notifications within. Note that these are not unread - * mentions - they are simply messages which the user has asked to cause a - * badge count update or push notification. - */ - Grey = "GREY", - /** - * The room has unread messages within (grey without the badge). - */ - Bold = "BOLD", - /** - * The room has no relevant unread messages within. - */ - Idle = "IDLE", -} +import { NotificationColor } from "../../../notifications/NotificationColor"; +import { RoomNotificationStateStore } from "../../../notifications/RoomNotificationStateStore"; interface ICategorizedRoomMap { // @ts-ignore - TS wants this to be a string, but we know better - [category: Category]: Room[]; + [category: NotificationColor]: Room[]; } interface ICategoryIndex { // @ts-ignore - TS wants this to be a string, but we know better - [category: Category]: number; // integer + [category: NotificationColor]: number; // integer } // Caution: changing this means you'll need to update a bunch of assumptions and // comments! Check the usage of Category carefully to figure out what needs changing // if you're going to change this array's order. -const CATEGORY_ORDER = [Category.Red, Category.Grey, Category.Bold, Category.Idle]; +const CATEGORY_ORDER = [ + NotificationColor.Red, + NotificationColor.Grey, + NotificationColor.Bold, + NotificationColor.None, // idle +]; /** * An implementation of the "importance" algorithm for room list sorting. Where @@ -92,10 +74,10 @@ export class ImportanceAlgorithm extends OrderingAlgorithm { // noinspection JSMethodCanBeStatic private categorizeRooms(rooms: Room[]): ICategorizedRoomMap { const map: ICategorizedRoomMap = { - [Category.Red]: [], - [Category.Grey]: [], - [Category.Bold]: [], - [Category.Idle]: [], + [NotificationColor.Red]: [], + [NotificationColor.Grey]: [], + [NotificationColor.Bold]: [], + [NotificationColor.None]: [], }; for (const room of rooms) { const category = this.getRoomCategory(room); @@ -105,25 +87,11 @@ export class ImportanceAlgorithm extends OrderingAlgorithm { } // noinspection JSMethodCanBeStatic - private getRoomCategory(room: Room): Category { - // Function implementation borrowed from old RoomListStore - - const mentions = room.getUnreadNotificationCount('highlight') > 0; - if (mentions) { - return Category.Red; - } - - let unread = room.getUnreadNotificationCount() > 0; - if (unread) { - return Category.Grey; - } - - unread = Unread.doesRoomHaveUnreadMessages(room); - if (unread) { - return Category.Bold; - } - - return Category.Idle; + private getRoomCategory(room: Room): NotificationColor { + // It's fine for us to call this a lot because it's cached, and we shouldn't be + // wasting anything by doing so as the store holds single references + const state = RoomNotificationStateStore.instance.getRoomState(room, this.tagId); + return state.color; } public async setRooms(rooms: Room[]): Promise { @@ -217,7 +185,7 @@ export class ImportanceAlgorithm extends OrderingAlgorithm { } } - private async sortCategory(category: Category) { + private async 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 @@ -234,7 +202,7 @@ export class ImportanceAlgorithm extends OrderingAlgorithm { } // noinspection JSMethodCanBeStatic - private getCategoryFromIndices(index: number, indices: ICategoryIndex): Category { + private getCategoryFromIndices(index: number, indices: ICategoryIndex): NotificationColor { for (let i = 0; i < CATEGORY_ORDER.length; i++) { const category = CATEGORY_ORDER[i]; const isLast = i === (CATEGORY_ORDER.length - 1); @@ -250,7 +218,7 @@ export class ImportanceAlgorithm extends OrderingAlgorithm { } // noinspection JSMethodCanBeStatic - private moveRoomIndexes(nRooms: number, fromCategory: Category, toCategory: Category, indices: ICategoryIndex) { + private moveRoomIndexes(nRooms: number, fromCategory: NotificationColor, toCategory: NotificationColor, indices: ICategoryIndex) { // We have to update the index of the category *after* the from/toCategory variables // in order to update the indices correctly. Because the room is moving from/to those // categories, the next category's index will change - not the category we're modifying. @@ -261,7 +229,7 @@ export class ImportanceAlgorithm extends OrderingAlgorithm { this.alterCategoryPositionBy(toCategory, +nRooms, indices); } - private alterCategoryPositionBy(category: Category, n: number, indices: ICategoryIndex) { + private alterCategoryPositionBy(category: NotificationColor, n: number, indices: ICategoryIndex) { // Note: when we alter a category's index, we actually have to modify the ones following // the target and not the target itself. From 213e2df9fced040e4e143dfc32259abecfb9cc33 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 12 Jul 2020 08:49:04 +0100 Subject: [PATCH 20/29] Remove redundant scroll-margins and fix RoomTile wrongly scrolling Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/views/rooms/_RoomTile2.scss | 9 --------- src/components/views/rooms/RoomSublist2.tsx | 10 ++++++++-- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/res/css/views/rooms/_RoomTile2.scss b/res/css/views/rooms/_RoomTile2.scss index d2d394e266..8297d1952c 100644 --- a/res/css/views/rooms/_RoomTile2.scss +++ b/res/css/views/rooms/_RoomTile2.scss @@ -21,10 +21,6 @@ limitations under the License. margin-bottom: 4px; padding: 4px; - // allow scrollIntoView to ignore the sticky headers, must match combined height of .mx_RoomSublist2_headerContainer - scroll-margin-top: 32px; - scroll-margin-bottom: 32px; - // The tile is also a flexbox row itself display: flex; @@ -168,11 +164,6 @@ limitations under the License. } } -// do not apply scroll-margin-bottom to the sublist which will not have a sticky header below it -.mx_RoomSublist2:last-child .mx_RoomTile2 { - scroll-margin-bottom: 0; -} - // We use these both in context menus and the room tiles .mx_RoomTile2_iconBell::before { mask-image: url('$(res)/img/feather-customised/bell.svg'); diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index c351384be4..5cb86d4cba 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -17,7 +17,7 @@ limitations under the License. */ import * as React from "react"; -import { createRef } from "react"; +import {createRef, UIEventHandler} from "react"; import { Room } from "matrix-js-sdk/src/models/room"; import classNames from 'classnames'; import { RovingAccessibleButton, RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex"; @@ -595,6 +595,12 @@ export default class RoomSublist2 extends React.Component { ); } + private onScrollPrevent(e: React.UIEvent) { + // the RoomTile calls scrollIntoView and the browser may scroll a div we do not wish to be scrollable + // this fixes https://github.com/vector-im/riot-web/issues/14413 + (e.target as HTMLDivElement).scrollTop = 0; + } + public render(): React.ReactElement { // TODO: Error boundary: https://github.com/vector-im/riot-web/issues/14185 @@ -704,7 +710,7 @@ export default class RoomSublist2 extends React.Component { className="mx_RoomSublist2_resizeBox" enable={handles} > -
+
{visibleTiles}
{showNButton} From 3f51bb84e128370ac002d54b28ab3ed63d374cb1 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 12 Jul 2020 18:24:28 +0100 Subject: [PATCH 21/29] Fix RoomAvatar viewAvatarOnClick to work on actual avatars instead of default ones Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/avatars/RoomAvatar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/avatars/RoomAvatar.tsx b/src/components/views/avatars/RoomAvatar.tsx index 0947157652..907cc9f318 100644 --- a/src/components/views/avatars/RoomAvatar.tsx +++ b/src/components/views/avatars/RoomAvatar.tsx @@ -135,7 +135,7 @@ export default class RoomAvatar extends React.Component { ); } From 71ecd5dc85f794ff7798ee70b32690b199bd0406 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 12 Jul 2020 18:40:24 +0100 Subject: [PATCH 22/29] clean-up Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/avatars/RoomAvatar.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/views/avatars/RoomAvatar.tsx b/src/components/views/avatars/RoomAvatar.tsx index 907cc9f318..3317ed3a60 100644 --- a/src/components/views/avatars/RoomAvatar.tsx +++ b/src/components/views/avatars/RoomAvatar.tsx @@ -126,16 +126,16 @@ export default class RoomAvatar extends React.Component { }; public render() { - /*eslint no-unused-vars: ["error", { "ignoreRestSiblings": true }]*/ const {room, oobData, viewAvatarOnClick, ...otherProps} = this.props; const roomName = room ? room.name : oobData.name; return ( - ); } From d253c5883075f0e842e0dca730b4d95ab25f0a96 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 12 Jul 2020 19:06:47 +0100 Subject: [PATCH 23/29] Room List v2 Enter in the filter field should select the first result Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/LeftPanel2.tsx | 9 +++++++++ src/components/structures/RoomSearch.tsx | 3 +++ 2 files changed, 12 insertions(+) diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index f1f1ffd01f..8ec19ee26b 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -274,6 +274,14 @@ export default class LeftPanel2 extends React.Component { } }; + private onEnter = () => { + const firstRoom = this.listContainerRef.current.querySelector(".mx_RoomTile2"); + if (firstRoom) { + firstRoom.click(); + this.onSearch(""); // clear the search field + } + }; + private onMoveFocus = (up: boolean) => { let element = this.focusedElement; @@ -346,6 +354,7 @@ export default class LeftPanel2 extends React.Component { onQueryUpdate={this.onSearch} isMinimized={this.props.isMinimized} onVerticalArrow={this.onKeyDown} + onEnter={this.onEnter} /> void; isMinimized: boolean; onVerticalArrow(ev: React.KeyboardEvent); + onEnter(ev: React.KeyboardEvent); } interface IState { @@ -115,6 +116,8 @@ export default class RoomSearch extends React.PureComponent { defaultDispatcher.fire(Action.FocusComposer); } else if (ev.key === Key.ARROW_UP || ev.key === Key.ARROW_DOWN) { this.props.onVerticalArrow(ev); + } else if (ev.key === Key.ENTER) { + this.props.onEnter(ev); } }; From c3789245b869e3b9a44273e5da1002cfb5a4ead3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 12 Jul 2020 19:25:43 +0100 Subject: [PATCH 24/29] Be consistent with the at-room pill avatar configurability Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/utils/pillify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/pillify.js b/src/utils/pillify.js index f708ab7770..cb140c61a4 100644 --- a/src/utils/pillify.js +++ b/src/utils/pillify.js @@ -111,7 +111,7 @@ export function pillifyLinks(nodes, mxEvent, pills) { type={Pill.TYPE_AT_ROOM_MENTION} inMessage={true} room={room} - shouldShowPillAvatar={true} + shouldShowPillAvatar={shouldShowPillAvatar} />; ReactDOM.render(pill, pillContainer); From 3e2280a6f46d1af601b069f4f551db6c256e195a Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Mon, 13 Jul 2020 15:43:34 +0100 Subject: [PATCH 25/29] Stop classname from overwritting baseavatar's --- src/components/views/avatars/BaseAvatar.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/views/avatars/BaseAvatar.tsx b/src/components/views/avatars/BaseAvatar.tsx index aa2c0ea954..7f30a7a377 100644 --- a/src/components/views/avatars/BaseAvatar.tsx +++ b/src/components/views/avatars/BaseAvatar.tsx @@ -99,6 +99,7 @@ const BaseAvatar = (props: IProps) => { defaultToInitialLetter = true, onClick, inputRef, + className, ...otherProps } = props; @@ -138,7 +139,7 @@ const BaseAvatar = (props: IProps) => { @@ -149,7 +150,7 @@ const BaseAvatar = (props: IProps) => { } else { return ( { if (onClick !== null) { return ( { } else { return ( Date: Mon, 13 Jul 2020 16:35:03 +0100 Subject: [PATCH 26/29] Fix room tile context menu for Historical rooms Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/MatrixChat.tsx | 21 +++++++++++++++++- src/components/structures/RoomView.js | 12 +++------- src/components/views/rooms/RoomTile2.tsx | 28 ++++++++++++++++++++++-- 3 files changed, 49 insertions(+), 12 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 89ee1bc22d..7b439d2303 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -50,7 +50,7 @@ import PageTypes from '../../PageTypes'; import { getHomePageUrl } from '../../utils/pages'; import createRoom from "../../createRoom"; -import { _t, getCurrentLanguage } from '../../languageHandler'; +import {_t, _td, getCurrentLanguage} from '../../languageHandler'; import SettingsStore, { SettingLevel } from "../../settings/SettingsStore"; import ThemeController from "../../settings/controllers/ThemeController"; import { startAnyRegistrationFlow } from "../../Registration.js"; @@ -554,6 +554,9 @@ export default class MatrixChat extends React.PureComponent { case 'leave_room': this.leaveRoom(payload.room_id); break; + case 'forget_room': + this.forgetRoom(payload.room_id); + break; case 'reject_invite': Modal.createTrackedDialog('Reject invitation', '', QuestionDialog, { title: _t('Reject invitation'), @@ -1124,6 +1127,22 @@ export default class MatrixChat extends React.PureComponent { }); } + private forgetRoom(roomId: string) { + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + MatrixClientPeg.get().forget(roomId).then(() => { + // Switch to another room view if we're currently viewing the historical room + if (this.state.currentRoomId === roomId) { + dis.dispatch({ action: "view_next_room" }); + } + }, function(err) { + const errCode = err.errcode || _td("unknown error code"); + Modal.createTrackedDialog("Failed to forget room", '', ErrorDialog, { + title: _t("Failed to forget room %(errCode)s", {errCode}), + description: ((err && err.message) ? err.message : _t("Operation failed")), + }); + }); + } + /** * Starts a chat with the welcome user, if the user doesn't already have one * @returns {string} The room ID of the new room, or null if no room was created diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index a9f75ce632..197acca599 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -1380,15 +1380,9 @@ export default createReactClass({ }, onForgetClick: function() { - this.context.forget(this.state.room.roomId).then(function() { - dis.dispatch({ action: 'view_next_room' }); - }, function(err) { - const errCode = err.errcode || _t("unknown error code"); - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Failed to forget room', '', ErrorDialog, { - title: _t("Error"), - description: _t("Failed to forget room %(errCode)s", { errCode: errCode }), - }); + dis.dispatch({ + action: 'forget_room', + room_id: this.state.room.roomId, }); }, diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index ed188e996b..ca2f8865f9 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -276,6 +276,17 @@ export default class RoomTile2 extends React.Component { this.setState({generalMenuPosition: null}); // hide the menu }; + private onForgetRoomClick = (ev: ButtonEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + + dis.dispatch({ + action: 'forget_room', + room_id: this.props.room.roomId, + }); + this.setState({generalMenuPosition: null}); // hide the menu + }; + private onOpenRoomSettings = (ev: ButtonEvent) => { ev.preventDefault(); ev.stopPropagation(); @@ -315,7 +326,7 @@ export default class RoomTile2 extends React.Component { private onClickMute = ev => this.saveNotifState(ev, MUTE); private renderNotificationsMenu(isActive: boolean): React.ReactElement { - if (MatrixClientPeg.get().isGuest() || !this.showContextMenu) { + if (MatrixClientPeg.get().isGuest() || this.props.tag === DefaultTagID.Archived || !this.showContextMenu) { // the menu makes no sense in these cases so do not show one return null; } @@ -397,7 +408,20 @@ export default class RoomTile2 extends React.Component { const favouriteLabel = isFavorite ? _t("Favourited") : _t("Favourite"); let contextMenu = null; - if (this.state.generalMenuPosition) { + if (this.state.generalMenuPosition && this.props.tag === DefaultTagID.Archived) { + contextMenu = ( + +
+
+ + + {_t("Forget Room")} + +
+
+
+ ); + } else if (this.state.generalMenuPosition) { contextMenu = (
From b3c3ef594edc9f2047e4caeb23e34f2a43f877cd Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 13 Jul 2020 16:39:59 +0100 Subject: [PATCH 27/29] i18n Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/i18n/strings/en_EN.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index fb97bfa26c..c8d311f690 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1233,6 +1233,7 @@ "Favourited": "Favourited", "Favourite": "Favourite", "Leave Room": "Leave Room", + "Forget Room": "Forget Room", "Room options": "Room options", "Add a topic": "Add a topic", "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.", From 3060cdf9345f6ed47fe41aa78e7a79b9fe98e95a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 13 Jul 2020 17:01:50 +0100 Subject: [PATCH 28/29] Iterate PR Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/MatrixChat.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 7b439d2303..fc918521c2 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -74,6 +74,7 @@ import { } from "../../toasts/AnalyticsToast"; import {showToast as showNotificationsToast} from "../../toasts/DesktopNotificationsToast"; import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload"; +import ErrorDialog from "../views/dialogs/ErrorDialog"; /** constants for MatrixChat.state.view */ export enum Views { @@ -460,7 +461,6 @@ export default class MatrixChat extends React.PureComponent { onAction = (payload) => { // console.log(`MatrixClientPeg.onAction: ${payload.action}`); - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); // Start the onboarding process for certain actions @@ -1063,7 +1063,6 @@ export default class MatrixChat extends React.PureComponent { private leaveRoom(roomId: string) { const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const roomToLeave = MatrixClientPeg.get().getRoom(roomId); const warnings = this.leaveRoomWarnings(roomId); @@ -1128,13 +1127,12 @@ export default class MatrixChat extends React.PureComponent { } private forgetRoom(roomId: string) { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); MatrixClientPeg.get().forget(roomId).then(() => { // Switch to another room view if we're currently viewing the historical room if (this.state.currentRoomId === roomId) { dis.dispatch({ action: "view_next_room" }); } - }, function(err) { + }).catch((err) => { const errCode = err.errcode || _td("unknown error code"); Modal.createTrackedDialog("Failed to forget room", '', ErrorDialog, { title: _t("Failed to forget room %(errCode)s", {errCode}), @@ -1391,7 +1389,6 @@ export default class MatrixChat extends React.PureComponent { return; } - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createTrackedDialog('Signed out', '', ErrorDialog, { title: _t('Signed Out'), description: _t('For security, this session has been signed out. Please sign in again.'), @@ -1461,7 +1458,6 @@ export default class MatrixChat extends React.PureComponent { } }); cli.on("crypto.warning", (type) => { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); switch (type) { case 'CRYPTO_WARNING_OLD_VERSION_DETECTED': Modal.createTrackedDialog('Crypto migrated', '', ErrorDialog, { From fd8f43e245269f16c86d73039a6b3e57608c8d68 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 13 Jul 2020 17:17:05 +0100 Subject: [PATCH 29/29] Fix room sub list header collapse/jump interactions on bottom-most sublist Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/RoomSublist2.tsx | 26 ++++++++++++--------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index 3623b8d48d..c22e6cd807 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -321,25 +321,29 @@ export default class RoomSublist2 extends React.Component { } }; - private onHeaderClick = (ev: React.MouseEvent) => { - let target = ev.target as HTMLDivElement; - if (!target.classList.contains('mx_RoomSublist2_headerText')) { - // If we don't have the headerText class, the user clicked the span in the headerText. - target = target.parentElement as HTMLDivElement; - } - - const possibleSticky = target.parentElement; + private onHeaderClick = () => { + const possibleSticky = this.headerButton.current.parentElement; const sublist = possibleSticky.parentElement.parentElement; const list = sublist.parentElement.parentElement; - // the scrollTop is capped at the height of the header in LeftPanel2 + // the scrollTop is capped at the height of the header in LeftPanel2, the top header is always sticky const isAtTop = list.scrollTop <= HEADER_HEIGHT; - const isSticky = possibleSticky.classList.contains('mx_RoomSublist2_headerContainer_sticky'); - if (isSticky && !isAtTop) { + const isAtBottom = list.scrollTop >= list.scrollHeight - list.offsetHeight; + const isStickyTop = possibleSticky.classList.contains('mx_RoomSublist2_headerContainer_stickyTop'); + const isStickyBottom = possibleSticky.classList.contains('mx_RoomSublist2_headerContainer_stickyBottom'); + + if ((isStickyBottom && !isAtBottom) || (isStickyTop && !isAtTop)) { // is sticky - jump to list sublist.scrollIntoView({behavior: 'smooth'}); } else { // on screen - toggle collapse + const isExpanded = this.state.isExpanded; this.toggleCollapsed(); + // if the bottom list is collapsed then scroll it in so it doesn't expand off screen + if (!isExpanded && isStickyBottom) { + setImmediate(() => { + sublist.scrollIntoView({behavior: 'smooth'}); + }); + } } };