From 3c2101fa313cb44b48fd56b1b301eb14bbffa206 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 19 Mar 2020 15:52:17 +0000 Subject: [PATCH 1/6] Fix ctrlOrCmd + Slash shortcut to match the guide Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/LoggedInView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index 576ae2b276..f923c50b44 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -380,7 +380,7 @@ const LoggedInView = createReactClass({ break; case Key.SLASH: - if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) { + if (ctrlCmdOnly) { KeyboardShortcuts.toggleDialog(); handled = true; } From 578b3f2b97d907bbba1bb7a8366e7d61ae0b4ed5 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 19 Mar 2020 19:07:33 +0000 Subject: [PATCH 2/6] Improve Keyboard Shortcuts. Add alt-arrows & alt-shift-arrows. Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../dialogs/_KeyboardShortcutsDialog.scss | 2 +- src/accessibility/KeyboardShortcuts.tsx | 44 ++++++++++++--- src/components/structures/LoggedInView.js | 12 +++++ src/components/views/rooms/RoomList.js | 53 +++++++++++++++++++ src/components/views/rooms/RoomTile.js | 18 ++++++- .../views/rooms/SendMessageComposer.js | 8 +-- src/i18n/strings/en_EN.json | 4 +- 7 files changed, 127 insertions(+), 14 deletions(-) diff --git a/res/css/views/dialogs/_KeyboardShortcutsDialog.scss b/res/css/views/dialogs/_KeyboardShortcutsDialog.scss index f529b11059..638cacd41f 100644 --- a/res/css/views/dialogs/_KeyboardShortcutsDialog.scss +++ b/res/css/views/dialogs/_KeyboardShortcutsDialog.scss @@ -21,7 +21,7 @@ limitations under the License. -webkit-box-direction: normal; flex-direction: column; margin-bottom: -50px; - max-height: 700px; // XXX: this may need adjusting when adding new shortcuts + max-height: 1100px; // XXX: this may need adjusting when adding new shortcuts .mx_KeyboardShortcutsDialog_category { width: 33.3333%; // 3 columns diff --git a/src/accessibility/KeyboardShortcuts.tsx b/src/accessibility/KeyboardShortcuts.tsx index 618ed4755a..9ffeb9f72c 100644 --- a/src/accessibility/KeyboardShortcuts.tsx +++ b/src/accessibility/KeyboardShortcuts.tsx @@ -87,12 +87,6 @@ const shortcuts: Record = { key: Key.GREATER_THAN, }], description: _td("Toggle Quote"), - }, { - keybinds: [{ - modifiers: [CMD_OR_CTRL], - key: Key.M, - }], - description: _td("Toggle Markdown"), }, { keybinds: [{ modifiers: [Modifiers.SHIFT], @@ -115,6 +109,15 @@ const shortcuts: Record = { key: Key.END, }], description: _td("Jump to start/end of the composer"), + }, { + keybinds: [{ + modifiers: [Modifiers.CONTROL, Modifiers.ALT], + key: Key.ARROW_UP, + }, { + modifiers: [Modifiers.CONTROL, Modifiers.ALT], + key: Key.ARROW_DOWN, + }], + description: _td("Navigate composer history"), }, ], @@ -179,6 +182,24 @@ const shortcuts: Record = { key: Key.PAGE_DOWN, }], description: _td("Scroll up/down in the timeline"), + }, { + keybinds: [{ + modifiers: [Modifiers.ALT, Modifiers.SHIFT], + key: Key.ARROW_UP, + }, { + modifiers: [Modifiers.ALT, Modifiers.SHIFT], + key: Key.ARROW_DOWN, + }], + description: _td("Previous/next unread room or DM"), + }, { + keybinds: [{ + modifiers: [Modifiers.ALT], + key: Key.ARROW_UP, + }, { + modifiers: [Modifiers.ALT], + key: Key.ARROW_DOWN, + }], + description: _td("Previous/next room or DM"), }, { keybinds: [{ modifiers: [CMD_OR_CTRL], @@ -223,6 +244,14 @@ const shortcuts: Record = { ], }; +const categoryOrder = [ + Categories.COMPOSER, + Categories.CALLS, + Categories.ROOM_LIST, + Categories.AUTOCOMPLETE, + Categories.NAVIGATION, +]; + interface IModal { close: () => void; finished: Promise; @@ -289,7 +318,8 @@ export const toggleDialog = () => { return; } - const sections = Object.entries(shortcuts).map(([category, list]) => { + const sections = categoryOrder.map(category => { + const list = shortcuts[category]; return

{_t(category)}

{list.map(shortcut => )}
diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index f923c50b44..41fbd54991 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -385,6 +385,18 @@ const LoggedInView = createReactClass({ handled = true; } break; + + case Key.ARROW_UP: + case Key.ARROW_DOWN: + if (ev.altKey && !ev.ctrlKey && !ev.metaKey) { + dis.dispatch({ + action: 'view_room_delta', + delta: ev.key === Key.ARROW_UP ? -1 : 1, + unread: ev.shiftKey, + }); + handled = true; + } + break; } if (handled) { diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index 2143c7f1a8..aa4c106bb6 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -1,6 +1,7 @@ /* Copyright 2015, 2016 OpenMarket Ltd Copyright 2017, 2018 Vector Creations Ltd +Copyright 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -40,6 +41,8 @@ import * as Receipt from "../../../utils/Receipt"; import {Resizer} from '../../../resizer'; import {Layout, Distributor} from '../../../resizer/distributors/roomsublist2'; import {RovingTabIndexProvider} from "../../../accessibility/RovingTabIndex"; +import * as Unread from "../../../Unread"; +import RoomViewStore from "../../../stores/RoomViewStore"; const HIDE_CONFERENCE_CHANS = true; const STANDARD_TAGS_REGEX = /^(m\.(favourite|lowpriority|server_notice)|im\.vector\.fake\.(invite|recent|direct|archived))$/; @@ -242,6 +245,56 @@ export default createReactClass({ }); } break; + case 'view_room_delta': { + const currentRoomId = RoomViewStore.getRoomId(); + const { + "im.vector.fake.invite": inviteRooms, + "m.favourite": favouriteRooms, + [TAG_DM]: dmRooms, + "im.vector.fake.recent": recentRooms, + "m.lowpriority": lowPriorityRooms, + "im.vector.fake.archived": historicalRooms, + "m.server_notice": serverNoticeRooms, + ...tags + } = this.state.lists; + + const shownCustomTagRooms = Object.keys(tags).filter(tagName => { + return (!this.state.customTags || this.state.customTags[tagName]) && + !tagName.match(STANDARD_TAGS_REGEX); + }).map(tagName => tags[tagName]); + + // this order matches the one when generating the room sublists below. + let rooms = this._applySearchFilter([ + ...inviteRooms, + ...favouriteRooms, + ...dmRooms, + ...recentRooms, + // eslint-disable-next-line prefer-spread + ...[].concat.apply([], shownCustomTagRooms), + ...lowPriorityRooms, + ...historicalRooms, + ...serverNoticeRooms, + ], this.props.searchFilter); // TODO optimize + + if (payload.unread) { + // filter to only notification rooms (and our current active room so we can index properly) + rooms = rooms.filter(room => { + return room.roomId === currentRoomId || Unread.doesRoomHaveUnreadMessages(room); + }); + } + + const currentIndex = rooms.findIndex(room => room.roomId === currentRoomId); + + const [room] = rooms.slice((currentIndex + payload.delta) % rooms.length); + // console.log("DEBUG", currentIndex, room, rooms); + if (room) { + dis.dispatch({ + action: 'view_room', + room_id: room.roomId, + }); + } + break; + } } }, diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index e1624602f1..d4ccd243cf 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -17,7 +17,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; +import React, {createRef} from 'react'; import PropTypes from 'prop-types'; import createReactClass from 'create-react-class'; import classNames from 'classnames'; @@ -225,6 +225,16 @@ export default createReactClass({ case 'feature_custom_status_changed': this.forceUpdate(); break; + + case 'view_room': + // when the room is selected make sure its tile is visible, for breadcrumbs/keyboard shortcuts + if (payload.room_id === this.props.room.roomId && this._roomTile.current) { + this._roomTile.current.scrollIntoView({ + block: "nearest", + behavior: "auto", + }); + } + break; } }, @@ -234,6 +244,10 @@ export default createReactClass({ }); }, + UNSAFE_componentWillMount: function() { + this._roomTile = createRef(); + }, + componentDidMount: function() { /* We bind here rather than in the definition because otherwise we wind up with the method only being callable once every 500ms across all instances, which would be wrong */ @@ -538,7 +552,7 @@ export default createReactClass({ } return - + {({onFocus, isActive, ref}) => Date: Thu, 19 Mar 2020 22:07:22 +0000 Subject: [PATCH 3/6] open room sublist when using alt-arrows to navigate into it, refire scroll event Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/RoomSubList.js | 36 ++++++++++++++++-------- src/components/views/rooms/RoomTile.js | 1 + 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index fa2231328c..5ab8ab60ee 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -111,17 +111,31 @@ export default class RoomSubList extends React.PureComponent { } onAction = (payload) => { - // XXX: Previously RoomList would forceUpdate whenever on_room_read is dispatched, - // but this is no longer true, so we must do it here (and can apply the small - // optimisation of checking that we care about the room being read). - // - // Ultimately we need to transition to a state pushing flow where something - // explicitly notifies the components concerned that the notif count for a room - // has change (e.g. a Flux store). - if (payload.action === 'on_room_read' && - this.props.list.some((r) => r.roomId === payload.roomId) - ) { - this.forceUpdate(); + switch (payload.action) { + case 'on_room_read': + // XXX: Previously RoomList would forceUpdate whenever on_room_read is dispatched, + // but this is no longer true, so we must do it here (and can apply the small + // optimisation of checking that we care about the room being read). + // + // Ultimately we need to transition to a state pushing flow where something + // explicitly notifies the components concerned that the notif count for a room + // has change (e.g. a Flux store). + if (this.props.list.some((r) => r.roomId === payload.roomId)) { + this.forceUpdate(); + } + break; + + case 'view_room': + if (this.state.hidden && !this.props.forceExpand && + this.props.list.some((r) => r.roomId === payload.room_id) + ) { + this.onClick(); + // re-fire to scroll the room tile, normally it catches `view_room` but here it wasn't rendered yet. + dis.dispatch({ + action: 'scroll_room_tile', + room_id: payload.room_id, + }); + } } }; diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index d4ccd243cf..9a04887483 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -226,6 +226,7 @@ export default createReactClass({ this.forceUpdate(); break; + case 'scroll_room_tile': case 'view_room': // when the room is selected make sure its tile is visible, for breadcrumbs/keyboard shortcuts if (payload.room_id === this.props.room.roomId && this._roomTile.current) { From f5d490ee78559b7b0889f3d36424bb311293131f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 19 Mar 2020 23:11:28 +0000 Subject: [PATCH 4/6] Tidy up code Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/RoomSubList.js | 5 ----- src/components/views/rooms/RoomTile.js | 23 ++++++++++++++++------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index 5ab8ab60ee..5ae240013c 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -130,11 +130,6 @@ export default class RoomSubList extends React.PureComponent { this.props.list.some((r) => r.roomId === payload.room_id) ) { this.onClick(); - // re-fire to scroll the room tile, normally it catches `view_room` but here it wasn't rendered yet. - dis.dispatch({ - action: 'scroll_room_tile', - room_id: payload.room_id, - }); } } }; diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 9a04887483..0f44f5077a 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -226,19 +226,23 @@ export default createReactClass({ this.forceUpdate(); break; - case 'scroll_room_tile': case 'view_room': - // when the room is selected make sure its tile is visible, for breadcrumbs/keyboard shortcuts - if (payload.room_id === this.props.room.roomId && this._roomTile.current) { - this._roomTile.current.scrollIntoView({ - block: "nearest", - behavior: "auto", - }); + // when the room is selected make sure its tile is visible, for breadcrumbs/keyboard shortcut access + if (payload.room_id === this.props.room.roomId) { + this._scrollIntoView(); } break; } }, + _scrollIntoView: function() { + if (!this._roomTile.current) return; + this._roomTile.current.scrollIntoView({ + block: "nearest", + behavior: "auto", + }); + }, + _onActiveRoomChange: function() { this.setState({ selected: this.props.room.roomId === RoomViewStore.getRoomId(), @@ -272,6 +276,11 @@ export default createReactClass({ statusUser.on("User._unstable_statusMessage", this._onStatusMessageCommitted); } } + + // when we're first rendered (or our sublist is expanded) make sure we are visible if we're active + if (this.state.selected) { + this._scrollIntoView(); + } }, componentWillUnmount: function() { From 4da7ce10de46eff134d93d3f2fda7dd1ce639e23 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 19 Mar 2020 23:15:05 +0000 Subject: [PATCH 5/6] delint Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/RoomList.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index aa4c106bb6..c00f0c920b 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -269,12 +269,11 @@ export default createReactClass({ ...favouriteRooms, ...dmRooms, ...recentRooms, - // eslint-disable-next-line prefer-spread - ...[].concat.apply([], shownCustomTagRooms), + ...[].concat.apply([], shownCustomTagRooms), // eslint-disable-line prefer-spread ...lowPriorityRooms, ...historicalRooms, ...serverNoticeRooms, - ], this.props.searchFilter); // TODO optimize + ], this.props.searchFilter); if (payload.unread) { // filter to only notification rooms (and our current active room so we can index properly) @@ -284,9 +283,8 @@ export default createReactClass({ } const currentIndex = rooms.findIndex(room => room.roomId === currentRoomId); - + // use slice to account for looping around the start const [room] = rooms.slice((currentIndex + payload.delta) % rooms.length); - // console.log("DEBUG", currentIndex, room, rooms); if (room) { dis.dispatch({ action: 'view_room', From 8a0ee2fd34adfd5d7cdd03e625284458892f427a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 20 Mar 2020 16:00:50 +0000 Subject: [PATCH 6/6] iterate PR Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/RoomSubList.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index 5ae240013c..9428de3e22 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -129,12 +129,12 @@ export default class RoomSubList extends React.PureComponent { if (this.state.hidden && !this.props.forceExpand && this.props.list.some((r) => r.roomId === payload.room_id) ) { - this.onClick(); + this.toggle(); } } }; - onClick = (ev) => { + toggle = () => { if (this.isCollapsibleOnClick()) { // The header isCollapsible, so the click is to be interpreted as collapse and truncation logic const isHidden = !this.state.hidden; @@ -147,6 +147,10 @@ export default class RoomSubList extends React.PureComponent { } }; + onClick = (ev) => { + this.toggle(); + }; + onHeaderKeyDown = (ev) => { switch (ev.key) { case Key.ARROW_LEFT: