diff --git a/res/css/views/rooms/_NotificationBadge.scss b/res/css/views/rooms/_NotificationBadge.scss index 521f1dfc20..0e6d442cc1 100644 --- a/res/css/views/rooms/_NotificationBadge.scss +++ b/res/css/views/rooms/_NotificationBadge.scss @@ -48,15 +48,15 @@ limitations under the License. } &.mx_NotificationBadge_2char { - width: 16px; - height: 16px; - border-radius: 16px; + width: $font-16px; + height: $font-16px; + border-radius: $font-16px; } &.mx_NotificationBadge_3char { - width: 26px; - height: 16px; - border-radius: 16px; + width: $font-26px; + height: $font-16px; + border-radius: $font-16px; } // The following is the floating badge diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 89ee1bc22d..fc918521c2 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"; @@ -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 @@ -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'), @@ -1060,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); @@ -1124,6 +1126,21 @@ export default class MatrixChat extends React.PureComponent { }); } + private forgetRoom(roomId: string) { + 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" }); + } + }).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}), + 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 @@ -1372,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.'), @@ -1442,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, { 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/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'}); + }); + } } }; 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 = (
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4b1dfe2b8e..a0903658dd 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1232,6 +1232,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.", diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index 8686a3a054..f3a77e765b 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -30,7 +30,6 @@ import { TagWatcher } from "./TagWatcher"; import RoomViewStore from "../RoomViewStore"; import { Algorithm, LIST_UPDATED_EVENT } from "./algorithms/Algorithm"; import { EffectiveMembership, getEffectiveMembership } from "./membership"; -import { ListLayout } from "./ListLayout"; import { isNullOrUndefined } from "matrix-js-sdk/src/utils"; import RoomListLayoutStore from "./RoomListLayoutStore"; import { MarkedExecution } from "../../utils/MarkedExecution"; @@ -425,7 +424,8 @@ export class RoomListStore2 extends AsyncStore { // logic must match calculateListOrder private calculateTagSorting(tagId: TagID): SortAlgorithm { - const defaultSort = SortAlgorithm.Alphabetic; + const isDefaultRecent = tagId === DefaultTagID.Invite || tagId === DefaultTagID.DM; + const defaultSort = isDefaultRecent ? SortAlgorithm.Recent : SortAlgorithm.Alphabetic; const settingAlphabetical = SettingsStore.getValue("RoomList.orderAlphabetically", null, true); const definedSort = this.getTagSorting(tagId); const storedSort = this.getStoredTagSorting(tagId); diff --git a/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts b/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts index e7ca94ed95..154fd40b69 100644 --- a/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts +++ b/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts @@ -38,7 +38,11 @@ export class RecentAlgorithm implements IAlgorithm { // actually changed (probably needs to be done higher up?) then we could do an // insertion sort or similar on the limited set of changes. - const myUserId = MatrixClientPeg.get().getUserId(); + // TODO: Don't assume we're using the same client as the peg + let myUserId = ''; + if (MatrixClientPeg.get()) { + myUserId = MatrixClientPeg.get().getUserId(); + } const tsCache: { [roomId: string]: number } = {}; const getLastTs = (r: Room) => { @@ -68,7 +72,6 @@ export class RecentAlgorithm implements IAlgorithm { const ev = r.timeline[i]; if (!ev.getTs()) continue; // skip events that don't have timestamps (tests only?) - // TODO: Don't assume we're using the same client as the peg if (ev.getSender() === myUserId || Unread.eventTriggersUnreadCount(ev)) { return ev.getTs(); }