From 161c37d93ccedf63ea7fe056781667e32cc4dc5c Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 1 Jul 2020 14:21:35 +0100 Subject: [PATCH 01/74] Upgrade matrix-js-sdk to 7.1.0-rc.1 --- package.json | 2 +- yarn.lock | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index e8719520c4..dd253dcc48 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "is-ip": "^2.0.0", "linkifyjs": "^2.1.6", "lodash": "^4.17.14", - "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", + "matrix-js-sdk": "7.1.0-rc.1", "minimist": "^1.2.0", "pako": "^1.0.5", "parse5": "^5.1.1", diff --git a/yarn.lock b/yarn.lock index c20658f014..ff73c6e6e0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5820,9 +5820,10 @@ mathml-tag-names@^2.0.1: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": - version "7.0.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/f683f4544aa5da150836b01c754062809119fa97" +matrix-js-sdk@7.1.0-rc.1: + version "7.1.0-rc.1" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-7.1.0-rc.1.tgz#eba6232c5980010783f0d4987421e36d4b365ead" + integrity sha512-h0KmA5RcY7Ui2406Et2plrwQRk8R6r3dBdsihaE6XIbtFaqQgJ4Q/eECFphHU20HA2SXrqXVcgvAIfds5y27aw== dependencies: "@babel/runtime" "^7.8.3" another-json "^0.2.0" From 012354239d6aa48277d873fff5574031c9d4b632 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 1 Jul 2020 14:32:19 +0100 Subject: [PATCH 02/74] Prepare changelog for v2.9.0-rc.1 --- CHANGELOG.md | 148 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cc850822e..948583937d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,151 @@ +Changes in [2.9.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.9.0-rc.1) (2020-07-01) +============================================================================================================= +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.8.1...v2.9.0-rc.1) + + * Upgrade to JS SDK 7.1.0-rc.1 + * Update from Weblate + [\#4869](https://github.com/matrix-org/matrix-react-sdk/pull/4869) + * Fix a number of proliferation issues in the new room list + [\#4828](https://github.com/matrix-org/matrix-react-sdk/pull/4828) + * Fix jumping to read marker for events without tiles + [\#4860](https://github.com/matrix-org/matrix-react-sdk/pull/4860) + * De-duplicate rooms from the room autocomplete provider + [\#4859](https://github.com/matrix-org/matrix-react-sdk/pull/4859) + * Add file upload button to recovery key input + [\#4847](https://github.com/matrix-org/matrix-react-sdk/pull/4847) + * Implement new design on security setup & login + [\#4831](https://github.com/matrix-org/matrix-react-sdk/pull/4831) + * Fix /join slash command via servers including room id as a via + [\#4856](https://github.com/matrix-org/matrix-react-sdk/pull/4856) + * Add Generic Expiring Toast and timing hooks + [\#4855](https://github.com/matrix-org/matrix-react-sdk/pull/4855) + * Fix Room Custom Sounds regression and make ProgressBar relevant again + [\#4846](https://github.com/matrix-org/matrix-react-sdk/pull/4846) + * Including start_sso and start_cas in redirect loop prevention + [\#4854](https://github.com/matrix-org/matrix-react-sdk/pull/4854) + * Clean up TODO comments for new room list + [\#4850](https://github.com/matrix-org/matrix-react-sdk/pull/4850) + * Show timestamp of redaction on hover + [\#4622](https://github.com/matrix-org/matrix-react-sdk/pull/4622) + * Remove the DM button from new room tiles + [\#4849](https://github.com/matrix-org/matrix-react-sdk/pull/4849) + * Hide room list show less button if it would do nothing + [\#4848](https://github.com/matrix-org/matrix-react-sdk/pull/4848) + * Improve message preview copy in new room list + [\#4823](https://github.com/matrix-org/matrix-react-sdk/pull/4823) + * Allow the tag panel to be disabled in the new room list + [\#4844](https://github.com/matrix-org/matrix-react-sdk/pull/4844) + * Make the whole user row clickable in the new room list + [\#4843](https://github.com/matrix-org/matrix-react-sdk/pull/4843) + * Add a new spinner design behind a labs flag + [\#4842](https://github.com/matrix-org/matrix-react-sdk/pull/4842) + * ts-ignore because something is made of fail + [\#4845](https://github.com/matrix-org/matrix-react-sdk/pull/4845) + * Fix Welcome.html CAS and SSO URLs not working + [\#4838](https://github.com/matrix-org/matrix-react-sdk/pull/4838) + * More small tweaks in preparation for Notifications rework + [\#4835](https://github.com/matrix-org/matrix-react-sdk/pull/4835) + * Iterate on the new room list resize handle + [\#4840](https://github.com/matrix-org/matrix-react-sdk/pull/4840) + * Update sublists for new hover states + [\#4837](https://github.com/matrix-org/matrix-react-sdk/pull/4837) + * Tweak parts of the new room list design + [\#4839](https://github.com/matrix-org/matrix-react-sdk/pull/4839) + * Implement new resize handle for dogfooding + [\#4836](https://github.com/matrix-org/matrix-react-sdk/pull/4836) + * Hide app badge count for hidden upgraded rooms (non-highlight) + [\#4834](https://github.com/matrix-org/matrix-react-sdk/pull/4834) + * Move compact modern layout checkbox to 'advanced' + [\#4822](https://github.com/matrix-org/matrix-react-sdk/pull/4822) + * Allow the user to resize the new sublists to 1 tile + [\#4825](https://github.com/matrix-org/matrix-react-sdk/pull/4825) + * Make LoggedInView a real component because it uses shouldComponentUpdate + [\#4832](https://github.com/matrix-org/matrix-react-sdk/pull/4832) + * Small tweaks in preparation for Notifications rework + [\#4829](https://github.com/matrix-org/matrix-react-sdk/pull/4829) + * Remove extraneous debug from the new left panel + [\#4826](https://github.com/matrix-org/matrix-react-sdk/pull/4826) + * Fix icons in the new user menu not showing up + [\#4824](https://github.com/matrix-org/matrix-react-sdk/pull/4824) + * Fix sticky room disappearing/jumping in search results + [\#4817](https://github.com/matrix-org/matrix-react-sdk/pull/4817) + * Show cross-signing / secret storage reset button in more cases + [\#4821](https://github.com/matrix-org/matrix-react-sdk/pull/4821) + * Use theme-capable icons in the user menu + [\#4819](https://github.com/matrix-org/matrix-react-sdk/pull/4819) + * Font support in custom themes + [\#4814](https://github.com/matrix-org/matrix-react-sdk/pull/4814) + * Decrease margin between new sublists + [\#4816](https://github.com/matrix-org/matrix-react-sdk/pull/4816) + * Update profile information in User Menu and truncate where needed + [\#4818](https://github.com/matrix-org/matrix-react-sdk/pull/4818) + * Fix MessageActionBar in irc layout + [\#4802](https://github.com/matrix-org/matrix-react-sdk/pull/4802) + * Mark messages with a black shield if the megolm session isn't trusted + [\#4797](https://github.com/matrix-org/matrix-react-sdk/pull/4797) + * Custom font selection + [\#4761](https://github.com/matrix-org/matrix-react-sdk/pull/4761) + * Use the correct timeline reference for message previews + [\#4812](https://github.com/matrix-org/matrix-react-sdk/pull/4812) + * Fix read receipt handling in the new room list + [\#4811](https://github.com/matrix-org/matrix-react-sdk/pull/4811) + * Improve unread/badge states in new room list (mk II) + [\#4805](https://github.com/matrix-org/matrix-react-sdk/pull/4805) + * Only fire setting changes for changed settings + [\#4803](https://github.com/matrix-org/matrix-react-sdk/pull/4803) + * Trigger room-specific watchers whenever a higher level change happens + [\#4804](https://github.com/matrix-org/matrix-react-sdk/pull/4804) + * Have the theme switcher set the device-level theme to match settings + [\#4810](https://github.com/matrix-org/matrix-react-sdk/pull/4810) + * Fix layout of minimized view for new room list + [\#4808](https://github.com/matrix-org/matrix-react-sdk/pull/4808) + * Fix sticky headers over/under extending themselves in the new room list + [\#4809](https://github.com/matrix-org/matrix-react-sdk/pull/4809) + * Update read receipt remainder for internal font size change + [\#4806](https://github.com/matrix-org/matrix-react-sdk/pull/4806) + * Fix some appearance tab crash and implement style nits + [\#4801](https://github.com/matrix-org/matrix-react-sdk/pull/4801) + * Add message preview for font slider + [\#4770](https://github.com/matrix-org/matrix-react-sdk/pull/4770) + * Add layout options to the appearance tab + [\#4773](https://github.com/matrix-org/matrix-react-sdk/pull/4773) + * Update from Weblate + [\#4800](https://github.com/matrix-org/matrix-react-sdk/pull/4800) + * Support accounts with cross signing but no SSSS + [\#4717](https://github.com/matrix-org/matrix-react-sdk/pull/4717) + * Look for existing verification requests after login + [\#4762](https://github.com/matrix-org/matrix-react-sdk/pull/4762) + * Add a checkpoint to index newly encrypted rooms. + [\#4611](https://github.com/matrix-org/matrix-react-sdk/pull/4611) + * Add support to paginate search results when using Seshat. + [\#4705](https://github.com/matrix-org/matrix-react-sdk/pull/4705) + * User versions in the event index. + [\#4788](https://github.com/matrix-org/matrix-react-sdk/pull/4788) + * Fix crash when filtering new room list too fast + [\#4796](https://github.com/matrix-org/matrix-react-sdk/pull/4796) + * hide search results from unknown rooms + [\#4795](https://github.com/matrix-org/matrix-react-sdk/pull/4795) + * Mark the new room list as ready for general testing + [\#4794](https://github.com/matrix-org/matrix-react-sdk/pull/4794) + * Extend QueryMatcher's sorting heuristic + [\#4784](https://github.com/matrix-org/matrix-react-sdk/pull/4784) + * Lint ts semicolons (aka. The great semicolon migration) + [\#4791](https://github.com/matrix-org/matrix-react-sdk/pull/4791) + * Revert "Use recovery keys over passphrases" + [\#4790](https://github.com/matrix-org/matrix-react-sdk/pull/4790) + * Clear `top` when not sticking headers to the top + [\#4783](https://github.com/matrix-org/matrix-react-sdk/pull/4783) + * Don't show a 'show less' button when it's impossible to collapse + [\#4785](https://github.com/matrix-org/matrix-react-sdk/pull/4785) + * Fix show less/more button occluding the list automatically + [\#4786](https://github.com/matrix-org/matrix-react-sdk/pull/4786) + * Improve room switching in the new room list + [\#4787](https://github.com/matrix-org/matrix-react-sdk/pull/4787) + * Remove labs option to cache 'passphrase' + [\#4789](https://github.com/matrix-org/matrix-react-sdk/pull/4789) + * Remove escape backslashes in non-Markdown messages + [\#4694](https://github.com/matrix-org/matrix-react-sdk/pull/4694) + Changes in [2.8.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.8.1) (2020-06-29) =================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.8.0...v2.8.1) From f9cb0b9cabd7df82cb34061bd1cb523c984d8600 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 1 Jul 2020 14:32:20 +0100 Subject: [PATCH 03/74] v2.9.0-rc.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dd253dcc48..b64784d3e3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "2.8.1", + "version": "2.9.0-rc.1", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From 289f40ce296bf34866abcd1b6785cc011119c980 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 2 Jul 2020 22:21:10 +0100 Subject: [PATCH 04/74] First step towards a11y in the new room list Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/views/rooms/_RoomSublist2.scss | 1 + res/css/views/rooms/_RoomTile2.scss | 9 +- src/components/structures/LeftPanel2.tsx | 97 +++++++++++++++++++-- src/components/structures/RoomSearch.tsx | 3 + src/components/views/rooms/RoomSublist2.tsx | 62 +++++++++++-- src/components/views/rooms/RoomTile2.tsx | 5 +- 6 files changed, 158 insertions(+), 19 deletions(-) diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss index ffb96cf600..43d1b3c7b3 100644 --- a/res/css/views/rooms/_RoomSublist2.scss +++ b/res/css/views/rooms/_RoomSublist2.scss @@ -278,6 +278,7 @@ limitations under the License. } &.mx_RoomSublist2_hasMenuOpen, + &:not(.mx_RoomSublist2_minimized) > .mx_RoomSublist2_headerContainer:focus-within, &:not(.mx_RoomSublist2_minimized) > .mx_RoomSublist2_headerContainer:hover { .mx_RoomSublist2_menuButton { visibility: visible; diff --git a/res/css/views/rooms/_RoomTile2.scss b/res/css/views/rooms/_RoomTile2.scss index ba8d315d5a..1fd32d3555 100644 --- a/res/css/views/rooms/_RoomTile2.scss +++ b/res/css/views/rooms/_RoomTile2.scss @@ -24,7 +24,10 @@ limitations under the License. // The tile is also a flexbox row itself display: flex; - &.mx_RoomTile2_selected, &:hover, &.mx_RoomTile2_hasMenuOpen { + &.mx_RoomTile2_selected, + &:hover, + &:focus-within, + &.mx_RoomTile2_hasMenuOpen { background-color: $roomtile2-selected-bg-color; border-radius: 32px; } @@ -132,7 +135,9 @@ limitations under the License. } &:not(.mx_RoomTile2_minimized) { - &:hover, &.mx_RoomTile2_hasMenuOpen { + &:hover, + &:focus-within, + &.mx_RoomTile2_hasMenuOpen { // Hide the badge container on hover because it'll be a menu button .mx_RoomTile2_badgeContainer { width: 0; diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index 83d5b9e138..0cf52039fe 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -30,7 +30,8 @@ import { BreadcrumbsStore } from "../../stores/BreadcrumbsStore"; import { UPDATE_EVENT } from "../../stores/AsyncStore"; import ResizeNotifier from "../../utils/ResizeNotifier"; import SettingsStore from "../../settings/SettingsStore"; -import RoomListStore, { RoomListStore2, LISTS_UPDATE_EVENT } from "../../stores/room-list/RoomListStore2"; +import RoomListStore, { LISTS_UPDATE_EVENT } from "../../stores/room-list/RoomListStore2"; +import {Key} from "../../Keyboard"; // 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 @@ -57,6 +58,7 @@ interface IState { export default class LeftPanel2 extends React.Component { private listContainerRef: React.RefObject = createRef(); private tagPanelWatcherRef: string; + private focusedElement = null; // TODO: a11y: https://github.com/vector-im/riot-web/issues/14180 @@ -150,6 +152,77 @@ export default class LeftPanel2 extends React.Component { this.handleStickyHeaders(this.listContainerRef.current); }; + private onFocus = (ev: React.FocusEvent) => { + this.focusedElement = ev.target; + }; + + private onBlur = () => { + this.focusedElement = null; + }; + + private onKeyDown = (ev: React.KeyboardEvent) => { + if (!this.focusedElement) return; + + switch (ev.key) { + case Key.ARROW_UP: + case Key.ARROW_DOWN: + this.onMoveFocus(ev, ev.key === Key.ARROW_UP); + break; + } + }; + + private onMoveFocus = (ev: React.KeyboardEvent, up: boolean) => { + let element = this.focusedElement; + + // unclear why this isn't needed + // var descending = (up == this.focusDirection) ? this.focusDescending : !this.focusDescending; + // this.focusDirection = up; + + let descending = false; // are we currently descending or ascending through the DOM tree? + let classes; + + do { + const child = up ? element.lastElementChild : element.firstElementChild; + const sibling = up ? element.previousElementSibling : element.nextElementSibling; + + if (descending) { + if (child) { + element = child; + } else if (sibling) { + element = sibling; + } else { + descending = false; + element = element.parentElement; + } + } else { + if (sibling) { + element = sibling; + descending = true; + } else { + element = element.parentElement; + } + } + + if (element) { + classes = element.classList; + } + } while (element && !( + classes.contains("mx_RoomTile2") || + classes.contains("mx_RoomSublist2_headerText") || + classes.contains("mx_RoomSearch_input"))); + + if (element) { + ev.stopPropagation(); + ev.preventDefault(); + element.focus(); + this.focusedElement = element; + } else { + // if navigation is via up/down arrow-keys, trap in the widget so it doesn't send to composer + ev.stopPropagation(); + ev.preventDefault(); + } + }; + private renderHeader(): React.ReactNode { let breadcrumbs; if (this.state.showBreadcrumbs) { @@ -170,8 +243,12 @@ export default class LeftPanel2 extends React.Component { private renderSearchExplore(): React.ReactNode { return ( -
- +
+ {
); - // TODO: Determine what these onWhatever handlers do: https://github.com/vector-im/riot-web/issues/14180 const roomList = {/*TODO*/}} + onKeyDown={this.onKeyDown} resizeNotifier={null} collapsed={false} searchFilter={this.state.searchFilter} - onFocus={() => {/*TODO*/}} - onBlur={() => {/*TODO*/}} + onFocus={this.onFocus} + onBlur={this.onBlur} isMinimized={this.props.isMinimized} />; @@ -223,7 +299,12 @@ export default class LeftPanel2 extends React.Component { className={roomListClasses} onScroll={this.onScroll} ref={this.listContainerRef} - >{roomList}
+ // Firefox sometimes makes this element focusable due to + // overflow:scroll;, so force it out of tab order. + tabIndex={-1} + > + {roomList} + ); diff --git a/src/components/structures/RoomSearch.tsx b/src/components/structures/RoomSearch.tsx index 8e64353954..7ed2acf276 100644 --- a/src/components/structures/RoomSearch.tsx +++ b/src/components/structures/RoomSearch.tsx @@ -38,6 +38,7 @@ import { Action } from "../../dispatcher/actions"; interface IProps { onQueryUpdate: (newQuery: string) => void; isMinimized: boolean; + onVerticalArrow(ev: React.KeyboardEvent); } interface IState { @@ -111,6 +112,8 @@ export default class RoomSearch extends React.PureComponent { if (ev.key === Key.ESCAPE) { this.clearInput(); defaultDispatcher.fire(Action.FocusComposer); + } else if (ev.key === Key.ARROW_UP || ev.key === Key.ARROW_DOWN) { + this.props.onVerticalArrow(ev); } }; diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index 547aa5e67e..c1f2a9ac79 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -35,6 +35,7 @@ import { DefaultTagID, TagID } from "../../../stores/room-list/models"; import dis from "../../../dispatcher/dispatcher"; import NotificationBadge from "./NotificationBadge"; import { ListNotificationState } from "../../../stores/notifications/ListNotificationState"; +import { Key } from "../../../Keyboard"; // 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 @@ -80,6 +81,9 @@ interface IState { } export default class RoomSublist2 extends React.Component { + private headerButton = createRef(); + private sublistRef = createRef(); + constructor(props: IProps) { super(props); @@ -215,8 +219,52 @@ export default class RoomSublist2 extends React.Component { sublist.scrollIntoView({behavior: 'smooth'}); } else { // on screen - toggle collapse - this.props.layout.isCollapsed = !this.props.layout.isCollapsed; - this.forceUpdate(); // because the layout doesn't trigger an update + this.toggleCollapsed(); + } + }; + + private toggleCollapsed = () => { + this.props.layout.isCollapsed = !this.props.layout.isCollapsed; + this.forceUpdate(); // because the layout doesn't trigger an update + }; + + private onHeaderKeyDown = (ev: React.KeyboardEvent) => { + const isCollapsed = this.props.layout && this.props.layout.isCollapsed; + switch (ev.key) { + case Key.ARROW_LEFT: + ev.stopPropagation(); + if (!isCollapsed) { + // On ARROW_LEFT collapse the room sublist if it isn't already + this.toggleCollapsed(); + } + break; + case Key.ARROW_RIGHT: { + ev.stopPropagation(); + if (isCollapsed) { + // On ARROW_RIGHT expand the room sublist if it isn't already + this.toggleCollapsed(); + } else if (this.sublistRef.current) { + // otherwise focus the first room + const element = this.sublistRef.current.querySelector(".mx_RoomTile2") as HTMLDivElement; + if (element) { + element.focus(); + } + } + break; + } + } + }; + + private onKeyDown = (ev: React.KeyboardEvent) => { + switch (ev.key) { + // On ARROW_LEFT go to the sublist header + case Key.ARROW_LEFT: + ev.stopPropagation(); + this.headerButton.current.focus(); + break; + // Consume ARROW_RIGHT so it doesn't cause focus to get sent to composer + case Key.ARROW_RIGHT: + ev.stopPropagation(); } }; @@ -335,7 +383,6 @@ export default class RoomSublist2 extends React.Component { return ( {({onFocus, isActive, ref}) => { - // TODO: Use onFocus: https://github.com/vector-im/riot-web/issues/14180 const tabIndex = isActive ? 0 : -1; const badge = ( @@ -382,13 +429,13 @@ export default class RoomSublist2 extends React.Component { // doesn't become sticky. // The same applies to the notification badge. return ( -
-
+
+
{ ); } - // TODO: onKeyDown support: https://github.com/vector-im/riot-web/issues/14180 return (
{this.renderHeader()} {content} diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index 92c69e54e7..a85ebe7dd3 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -235,7 +235,7 @@ export default class RoomTile2 extends React.Component { private onClickMentions = ev => this.saveNotifState(ev, MENTIONS_ONLY); private onClickMute = ev => this.saveNotifState(ev, MUTE); - private renderNotificationsMenu(): React.ReactElement { + private renderNotificationsMenu(isActive: boolean): React.ReactElement { if (this.props.isMinimized || MatrixClientPeg.get().isGuest() || this.props.tag === DefaultTagID.Invite) { // the menu makes no sense in these cases so do not show one return null; @@ -296,6 +296,7 @@ export default class RoomTile2 extends React.Component { onClick={this.onNotificationsMenuOpenClick} label={_t("Notification options")} isExpanded={!!this.state.notificationsMenuPosition} + tabIndex={isActive ? 0 : -1} /> {contextMenu} @@ -434,7 +435,7 @@ export default class RoomTile2 extends React.Component { {roomAvatar} {nameContainer} {badge} - {this.renderNotificationsMenu()} + {this.renderNotificationsMenu(isActive)} {this.renderGeneralMenu()} } From 111d4adab2b87c5ed019b4aff6bd7df85c61f1b1 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 2 Jul 2020 22:38:11 +0100 Subject: [PATCH 05/74] Convert createRoom over to typescript Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/{createRoom.js => createRoom.ts} | 68 +++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 7 deletions(-) rename src/{createRoom.js => createRoom.ts} (81%) diff --git a/src/createRoom.js b/src/createRoom.ts similarity index 81% rename from src/createRoom.js rename to src/createRoom.ts index affdf196a7..b863450065 100644 --- a/src/createRoom.js +++ b/src/createRoom.ts @@ -15,6 +15,9 @@ See the License for the specific language governing permissions and limitations under the License. */ +import {MatrixClient} from "matrix-js-sdk/src/client"; +import {Room} from "matrix-js-sdk/src/models/Room"; + import {MatrixClientPeg} from './MatrixClientPeg'; import Modal from './Modal'; import * as sdk from './index'; @@ -26,6 +29,56 @@ import {getAddressType} from "./UserAddress"; const E2EE_WK_KEY = "im.vector.riot.e2ee"; +// TODO move these interfaces over to js-sdk once it has been typescripted enough to accept them +enum Visibility { + Public = "public", + Private = "private", +} + +enum Preset { + PrivateChat = "private_chat", + TrustedPrivateChat = "trusted_private_chat", + PublicChat = "public_chat", +} + +interface Invite3PID { + id_server: string; + id_access_token?: string; // this gets injected by the js-sdk + medium: string; + address: string; +} + +interface IStateEvent { + type: string; + state_key?: string; // defaults to an empty string + content: object; +} + +interface ICreateOpts { + visibility?: Visibility; + room_alias_name?: string; + name?: string; + topic?: string; + invite?: string[]; + invite_3pid?: Invite3PID[]; + room_version?: string; + creation_content?: object; + initial_state?: IStateEvent[]; + preset?: Preset; + is_direct?: boolean; + power_level_content_override?: object; +} + +interface IOpts { + dmUserId?: string; + createOpts?: ICreateOpts; + spinner?: boolean; + guestAccess?: boolean; + encryption?: boolean; + inlineErrors?: boolean; + andView?: boolean; +} + /** * Create a new room, and switch to it. * @@ -40,11 +93,12 @@ const E2EE_WK_KEY = "im.vector.riot.e2ee"; * Default: False * @param {bool=} opts.inlineErrors True to raise errors off the promise instead of resolving to null. * Default: False + * @param {bool=} opts.andView True to dispatch an action to view the room once it has been created. * * @returns {Promise} which resolves to the room id, or null if the * action was aborted or failed. */ -export default function createRoom(opts) { +export default function createRoom(opts: IOpts): Promise { opts = opts || {}; if (opts.spinner === undefined) opts.spinner = true; if (opts.guestAccess === undefined) opts.guestAccess = true; @@ -59,12 +113,12 @@ export default function createRoom(opts) { return Promise.resolve(null); } - const defaultPreset = opts.dmUserId ? 'trusted_private_chat' : 'private_chat'; + const defaultPreset = opts.dmUserId ? Preset.TrustedPrivateChat : Preset.PrivateChat; // set some defaults for the creation const createOpts = opts.createOpts || {}; createOpts.preset = createOpts.preset || defaultPreset; - createOpts.visibility = createOpts.visibility || 'private'; + createOpts.visibility = createOpts.visibility || Visibility.Private; if (opts.dmUserId && createOpts.invite === undefined) { switch (getAddressType(opts.dmUserId)) { case 'mx-user-id': @@ -166,7 +220,7 @@ export default function createRoom(opts) { }); } -export function findDMForUser(client, userId) { +export function findDMForUser(client: MatrixClient, userId: string): Room { const roomIds = DMRoomMap.shared().getDMRoomsForUserId(userId); const rooms = roomIds.map(id => client.getRoom(id)); const suitableDMRooms = rooms.filter(r => { @@ -189,7 +243,7 @@ export function findDMForUser(client, userId) { * NOTE: this assumes you've just created the room and there's not been an opportunity * for other code to run, so we shouldn't miss RoomState.newMember when it comes by. */ -export async function _waitForMember(client, roomId, userId, opts = { timeout: 1500 }) { +export async function _waitForMember(client: MatrixClient, roomId: string, userId: string, opts = { timeout: 1500 }) { const { timeout } = opts; let handler; return new Promise((resolve) => { @@ -212,7 +266,7 @@ export async function _waitForMember(client, roomId, userId, opts = { timeout: 1 * Ensure that for every user in a room, there is at least one device that we * can encrypt to. */ -export async function canEncryptToAllUsers(client, userIds) { +export async function canEncryptToAllUsers(client: MatrixClient, userIds: string[]) { const usersDeviceMap = await client.downloadKeys(userIds); // { "@user:host": { "DEVICE": {...}, ... }, ... } return Object.values(usersDeviceMap).every((userDevices) => @@ -221,7 +275,7 @@ export async function canEncryptToAllUsers(client, userIds) { ); } -export async function ensureDMExists(client, userId) { +export async function ensureDMExists(client: MatrixClient, userId: string): Promise { const existingDMRoom = findDMForUser(client, userId); let roomId; if (existingDMRoom) { From 97711032d8bb94848fabe959edc3ccf7daff2df9 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 2 Jul 2020 22:38:21 +0100 Subject: [PATCH 06/74] Fix signature of sleep utility Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/utils/promise.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/promise.ts b/src/utils/promise.ts index c5c1cb9a56..d3ae2c3d1b 100644 --- a/src/utils/promise.ts +++ b/src/utils/promise.ts @@ -15,7 +15,7 @@ limitations under the License. */ // Returns a promise which resolves with a given value after the given number of ms -export function sleep(ms: number, value: T): Promise { +export function sleep(ms: number, value?: T): Promise { return new Promise((resolve => { setTimeout(resolve, ms, value); })); } From 7322aaf6023784ae2bb1dbd556a911611bcd0af6 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 2 Jul 2020 22:42:28 +0100 Subject: [PATCH 07/74] Convert PlatformPeg to Typescript Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/@types/global.d.ts | 2 ++ src/{PlatformPeg.js => PlatformPeg.ts} | 15 ++++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) rename src/{PlatformPeg.js => PlatformPeg.ts} (82%) diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index ffd3277892..8486016cd0 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -20,6 +20,7 @@ import { IMatrixClientPeg } from "../MatrixClientPeg"; import ToastStore from "../stores/ToastStore"; import DeviceListener from "../DeviceListener"; import { RoomListStore2 } from "../stores/room-list/RoomListStore2"; +import { PlatformPeg } from "../PlatformPeg"; declare global { interface Window { @@ -33,6 +34,7 @@ declare global { mx_ToastStore: ToastStore; mx_DeviceListener: DeviceListener; mx_RoomListStore2: RoomListStore2; + mxPlatformPeg: PlatformPeg; } // workaround for https://github.com/microsoft/TypeScript/issues/30933 diff --git a/src/PlatformPeg.js b/src/PlatformPeg.ts similarity index 82% rename from src/PlatformPeg.js rename to src/PlatformPeg.ts index 34131fde7d..42cb7acaf7 100644 --- a/src/PlatformPeg.js +++ b/src/PlatformPeg.ts @@ -1,5 +1,6 @@ /* Copyright 2016 OpenMarket 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. @@ -14,6 +15,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +import BasePlatform from "./BasePlatform"; + /* * Holds the current Platform object used by the code to do anything * specific to the platform we're running on (eg. web, electron) @@ -21,10 +24,8 @@ limitations under the License. * This allows the app layer to set a Platform without necessarily * having to have a MatrixChat object */ -class PlatformPeg { - constructor() { - this.platform = null; - } +export class PlatformPeg { + platform: BasePlatform = null; /** * Returns the current Platform object for the application. @@ -44,7 +45,7 @@ class PlatformPeg { } } -if (!global.mxPlatformPeg) { - global.mxPlatformPeg = new PlatformPeg(); +if (!window.mxPlatformPeg) { + window.mxPlatformPeg = new PlatformPeg(); } -export default global.mxPlatformPeg; +export default window.mxPlatformPeg; From 1ab0a1a1defc1c75c54302ef5ac51c4b71d5ddf0 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 2 Jul 2020 23:14:31 +0100 Subject: [PATCH 08/74] First step towards a11y in the new room list Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/BasePlatform.ts | 4 ++++ src/PlatformPeg.ts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index 1d11495e61..acf72a986c 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -53,6 +53,10 @@ export default abstract class BasePlatform { this.startUpdateCheck = this.startUpdateCheck.bind(this); } + abstract async getConfig(): Promise<{}>; + + abstract getDefaultDeviceDisplayName(): string; + protected onAction = (payload: ActionPayload) => { switch (payload.action) { case 'on_client_not_viable': diff --git a/src/PlatformPeg.ts b/src/PlatformPeg.ts index 42cb7acaf7..1d2b813ebc 100644 --- a/src/PlatformPeg.ts +++ b/src/PlatformPeg.ts @@ -40,7 +40,7 @@ export class PlatformPeg { * application. * This should be an instance of a class extending BasePlatform. */ - set(plaf) { + set(plaf: BasePlatform) { this.platform = plaf; } } From 48ce294a497211eff1405b47ca1fc2219857b0db Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 2 Jul 2020 23:15:08 +0100 Subject: [PATCH 09/74] Transition languageHandler to Typescript Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- package.json | 1 + src/@types/global.d.ts | 4 ++ src/components/views/toasts/GenericToast.tsx | 4 +- ...languageHandler.js => languageHandler.tsx} | 72 ++++++++++++------- yarn.lock | 5 ++ 5 files changed, 57 insertions(+), 29 deletions(-) rename src/{languageHandler.js => languageHandler.tsx} (87%) diff --git a/package.json b/package.json index e8719520c4..a79a0467ff 100644 --- a/package.json +++ b/package.json @@ -120,6 +120,7 @@ "@babel/register": "^7.7.4", "@peculiar/webcrypto": "^1.0.22", "@types/classnames": "^2.2.10", + "@types/counterpart": "^0.18.1", "@types/flux": "^3.1.9", "@types/lodash": "^4.14.152", "@types/modernizr": "^3.5.3", diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index 8486016cd0..2c2fec759c 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -47,6 +47,10 @@ declare global { hasStorageAccess?: () => Promise; } + interface Navigator { + userLanguage?: string; + } + interface StorageEstimate { usageDetails?: {[key: string]: number}; } diff --git a/src/components/views/toasts/GenericToast.tsx b/src/components/views/toasts/GenericToast.tsx index 9f8885ba47..6cd881b9eb 100644 --- a/src/components/views/toasts/GenericToast.tsx +++ b/src/components/views/toasts/GenericToast.tsx @@ -14,13 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, {ReactChild} from "react"; +import React, {ReactNode} from "react"; import FormButton from "../elements/FormButton"; import {XOR} from "../../../@types/common"; export interface IProps { - description: ReactChild; + description: ReactNode; acceptLabel: string; onAccept(); diff --git a/src/languageHandler.js b/src/languageHandler.tsx similarity index 87% rename from src/languageHandler.js rename to src/languageHandler.tsx index 79a172015a..91d90d4e6c 100644 --- a/src/languageHandler.js +++ b/src/languageHandler.tsx @@ -1,7 +1,7 @@ /* Copyright 2017 MTRNord and Cooperative EITA Copyright 2017 Vector Creations Ltd. -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019, 2020 The Matrix.org Foundation C.I.C. Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,10 +20,11 @@ limitations under the License. import request from 'browser-request'; import counterpart from 'counterpart'; import React from 'react'; + import SettingsStore, {SettingLevel} from "./settings/SettingsStore"; import PlatformPeg from "./PlatformPeg"; -// $webapp is a webpack resolve alias pointing to the output directory, see webpack config +// @ts-ignore - $webapp is a webpack resolve alias pointing to the output directory, see webpack config import webpackLangJsonUrl from "$webapp/i18n/languages.json"; const i18nFolder = 'i18n/'; @@ -37,27 +38,31 @@ counterpart.setSeparator('|'); // Fall back to English counterpart.setFallbackLocale('en'); +interface ITranslatableError extends Error { + translatedMessage: string; +} + /** * Helper function to create an error which has an English message * with a translatedMessage property for use by the consumer. * @param {string} message Message to translate. * @returns {Error} The constructed error. */ -export function newTranslatableError(message) { - const error = new Error(message); +export function newTranslatableError(message: string) { + const error = new Error(message) as ITranslatableError; error.translatedMessage = _t(message); return error; } // Function which only purpose is to mark that a string is translatable // Does not actually do anything. It's helpful for automatic extraction of translatable strings -export function _td(s) { +export function _td(s: string): string { return s; } // Wrapper for counterpart's translation function so that it handles nulls and undefineds properly // Takes the same arguments as counterpart.translate() -function safeCounterpartTranslate(text, options) { +function safeCounterpartTranslate(text: string, options?: object) { // Horrible hack to avoid https://github.com/vector-im/riot-web/issues/4191 // The interpolation library that counterpart uses does not support undefined/null // values and instead will throw an error. This is a problem since everywhere else @@ -89,6 +94,13 @@ function safeCounterpartTranslate(text, options) { return translated; } +interface IVariables { + count?: number; + [key: string]: number | string; +} + +type Tags = Record React.ReactNode>; + /* * Translates text and optionally also replaces XML-ish elements in the text with e.g. React components * @param {string} text The untranslated text, e.g "click here now to %(foo)s". @@ -105,7 +117,9 @@ function safeCounterpartTranslate(text, options) { * * @return a React component if any non-strings were used in substitutions, otherwise a string */ -export function _t(text, variables, tags) { +export function _t(text: string, variables?: IVariables): string; +export function _t(text: string, variables: IVariables, tags: Tags): React.ReactNode; +export function _t(text: string, variables?: IVariables, tags?: Tags): string | React.ReactNode { // Don't do substitutions in counterpart. We handle it ourselves so we can replace with React components // However, still pass the variables to counterpart so that it can choose the correct plural if count is given // It is enough to pass the count variable, but in the future counterpart might make use of other information too @@ -141,23 +155,25 @@ export function _t(text, variables, tags) { * * @return a React component if any non-strings were used in substitutions, otherwise a string */ -export function substitute(text, variables, tags) { - let result = text; +export function substitute(text: string, variables?: IVariables): string; +export function substitute(text: string, variables: IVariables, tags: Tags): string; +export function substitute(text: string, variables?: IVariables, tags?: Tags): string | React.ReactNode { + let result: React.ReactNode | string = text; if (variables !== undefined) { - const regexpMapping = {}; + const regexpMapping: IVariables = {}; for (const variable in variables) { regexpMapping[`%\\(${variable}\\)s`] = variables[variable]; } - result = replaceByRegexes(result, regexpMapping); + result = replaceByRegexes(result as string, regexpMapping); } if (tags !== undefined) { - const regexpMapping = {}; + const regexpMapping: Tags = {}; for (const tag in tags) { regexpMapping[`(<${tag}>(.*?)<\\/${tag}>|<${tag}>|<${tag}\\s*\\/>)`] = tags[tag]; } - result = replaceByRegexes(result, regexpMapping); + result = replaceByRegexes(result as string, regexpMapping); } return result; @@ -172,7 +188,9 @@ export function substitute(text, variables, tags) { * * @return a React component if any non-strings were used in substitutions, otherwise a string */ -export function replaceByRegexes(text, mapping) { +export function replaceByRegexes(text: string, mapping: IVariables): string; +export function replaceByRegexes(text: string, mapping: Tags): React.ReactNode; +export function replaceByRegexes(text: string, mapping: IVariables | Tags): string | React.ReactNode { // We initially store our output as an array of strings and objects (e.g. React components). // This will then be converted to a string or a at the end const output = [text]; @@ -189,7 +207,7 @@ export function replaceByRegexes(text, mapping) { // and everything after the match. Insert all three into the output. We need to do this because we can insert objects. // Otherwise there would be no need for the splitting and we could do simple replacement. let matchFoundSomewhere = false; // If we don't find a match anywhere we want to log it - for (const outputIndex in output) { + for (let outputIndex = 0; outputIndex < output.length; outputIndex++) { const inputText = output[outputIndex]; if (typeof inputText !== 'string') { // We might have inserted objects earlier, don't try to replace them continue; @@ -216,7 +234,7 @@ export function replaceByRegexes(text, mapping) { let replaced; // If substitution is a function, call it if (mapping[regexpString] instanceof Function) { - replaced = mapping[regexpString].apply(null, capturedGroups); + replaced = (mapping as Tags)[regexpString].apply(null, capturedGroups); } else { replaced = mapping[regexpString]; } @@ -277,11 +295,11 @@ export function replaceByRegexes(text, mapping) { // Allow overriding the text displayed when no translation exists // Currently only used in unit tests to avoid having to load // the translations in riot-web -export function setMissingEntryGenerator(f) { +export function setMissingEntryGenerator(f: (value: string) => void) { counterpart.setMissingEntryGenerator(f); } -export function setLanguage(preferredLangs) { +export function setLanguage(preferredLangs: string | string[]) { if (!Array.isArray(preferredLangs)) { preferredLangs = [preferredLangs]; } @@ -358,8 +376,8 @@ export function getLanguageFromBrowser() { * @param {string} language The input language string * @return {string[]} List of normalised languages */ -export function getNormalizedLanguageKeys(language) { - const languageKeys = []; +export function getNormalizedLanguageKeys(language: string) { + const languageKeys: string[] = []; const normalizedLanguage = normalizeLanguageKey(language); const languageParts = normalizedLanguage.split('-'); if (languageParts.length === 2 && languageParts[0] === languageParts[1]) { @@ -380,7 +398,7 @@ export function getNormalizedLanguageKeys(language) { * @param {string} language The language string to be normalized * @returns {string} The normalized language string */ -export function normalizeLanguageKey(language) { +export function normalizeLanguageKey(language: string) { return language.toLowerCase().replace("_", "-"); } @@ -396,7 +414,7 @@ export function getCurrentLanguage() { * @param {string[]} langs List of language codes to pick from * @returns {string} The most appropriate language code from langs */ -export function pickBestLanguage(langs) { +export function pickBestLanguage(langs: string[]): string { const currentLang = getCurrentLanguage(); const normalisedLangs = langs.map(normalizeLanguageKey); @@ -408,13 +426,13 @@ export function pickBestLanguage(langs) { { // Failing that, a different dialect of the same language - const closeLangIndex = normalisedLangs.find((l) => l.substr(0, 2) === currentLang.substr(0, 2)); + const closeLangIndex = normalisedLangs.findIndex((l) => l.substr(0, 2) === currentLang.substr(0, 2)); if (closeLangIndex > -1) return langs[closeLangIndex]; } { // Neither of those? Try an english variant. - const enIndex = normalisedLangs.find((l) => l.startsWith('en')); + const enIndex = normalisedLangs.findIndex((l) => l.startsWith('en')); if (enIndex > -1) return langs[enIndex]; } @@ -422,7 +440,7 @@ export function pickBestLanguage(langs) { return langs[0]; } -function getLangsJson() { +function getLangsJson(): Promise { return new Promise(async (resolve, reject) => { let url; if (typeof(webpackLangJsonUrl) === 'string') { // in Jest this 'url' isn't a URL, so just fall through @@ -443,7 +461,7 @@ function getLangsJson() { }); } -function weblateToCounterpart(inTrs) { +function weblateToCounterpart(inTrs: object): object { const outTrs = {}; for (const key of Object.keys(inTrs)) { @@ -463,7 +481,7 @@ function weblateToCounterpart(inTrs) { return outTrs; } -function getLanguage(langPath) { +function getLanguage(langPath: string): object { return new Promise((resolve, reject) => { request( { method: "GET", url: langPath }, diff --git a/yarn.lock b/yarn.lock index c20658f014..b51f7af45b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1257,6 +1257,11 @@ resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.10.tgz#cc658ca319b6355399efc1f5b9e818f1a24bf999" integrity sha512-1UzDldn9GfYYEsWWnn/P4wkTlkZDH7lDb0wBMGbtIQc9zXEQq7FlKBdZUn6OBqD8sKZZ2RQO2mAjGpXiDGoRmQ== +"@types/counterpart@^0.18.1": + version "0.18.1" + resolved "https://registry.yarnpkg.com/@types/counterpart/-/counterpart-0.18.1.tgz#b1b784d9e54d9879f0a8cb12f2caedab65430fe8" + integrity sha512-PRuFlBBkvdDOtxlIASzTmkEFar+S66Ek48NVVTWMUjtJAdn5vyMSN8y6IZIoIymGpR36q2nZbIYazBWyFxL+IQ== + "@types/fbemitter@*": version "2.0.32" resolved "https://registry.yarnpkg.com/@types/fbemitter/-/fbemitter-2.0.32.tgz#8ed204da0f54e9c8eaec31b1eec91e25132d082c" From f69a090d3d438eca7ec09c7da9b2ac2719205fd4 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 2 Jul 2020 23:22:36 +0100 Subject: [PATCH 10/74] delint Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/createRoom.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/createRoom.ts b/src/createRoom.ts index b863450065..c436196c27 100644 --- a/src/createRoom.ts +++ b/src/createRoom.ts @@ -16,7 +16,7 @@ limitations under the License. */ import {MatrixClient} from "matrix-js-sdk/src/client"; -import {Room} from "matrix-js-sdk/src/models/Room"; +import {Room} from "matrix-js-sdk/src/models/room"; import {MatrixClientPeg} from './MatrixClientPeg'; import Modal from './Modal'; From 33612398bed6383501bb1c2327f5fdc41a35c11b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 2 Jul 2020 23:26:39 +0100 Subject: [PATCH 11/74] fix import Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/groups.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/groups.js b/src/groups.js index 860cf71fff..e73af15c79 100644 --- a/src/groups.js +++ b/src/groups.js @@ -15,7 +15,8 @@ limitations under the License. */ import PropTypes from 'prop-types'; -import { _t } from './languageHandler.js'; + +import { _t } from './languageHandler'; export const GroupMemberType = PropTypes.shape({ userId: PropTypes.string.isRequired, From 96cfd26bd42ea1437e4cc4486e1338ce45becc59 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 2 Jul 2020 23:32:21 +0100 Subject: [PATCH 12/74] fix imports some more Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/elements/EditableItemList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/elements/EditableItemList.js b/src/components/views/elements/EditableItemList.js index 50d5a3d10f..34e53906a2 100644 --- a/src/components/views/elements/EditableItemList.js +++ b/src/components/views/elements/EditableItemList.js @@ -16,7 +16,7 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import {_t} from '../../../languageHandler.js'; +import {_t} from '../../../languageHandler'; import Field from "./Field"; import AccessibleButton from "./AccessibleButton"; From d725cc338924421c2ef5d2f231cad699a8298a6c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 2 Jul 2020 23:39:27 +0100 Subject: [PATCH 13/74] convert MatrixClientContext to Typescript Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../{MatrixClientContext.js => MatrixClientContext.ts} | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) rename src/contexts/{MatrixClientContext.js => MatrixClientContext.ts} (85%) diff --git a/src/contexts/MatrixClientContext.js b/src/contexts/MatrixClientContext.ts similarity index 85% rename from src/contexts/MatrixClientContext.js rename to src/contexts/MatrixClientContext.ts index 54a23ca132..7e8a92064d 100644 --- a/src/contexts/MatrixClientContext.js +++ b/src/contexts/MatrixClientContext.ts @@ -15,7 +15,8 @@ limitations under the License. */ import { createContext } from "react"; +import { MatrixClient } from "matrix-js-sdk/src/client"; -const MatrixClientContext = createContext(undefined); +const MatrixClientContext = createContext(undefined); MatrixClientContext.displayName = "MatrixClientContext"; export default MatrixClientContext; From 04142a8723d2fb07983501ad52dc7b559b087ccf Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 3 Jul 2020 12:06:00 +0100 Subject: [PATCH 14/74] Remove duplicate compact settings, handle device level updates This removes the duplicate setting for compact layout from the appearance tab, and leaves the "advanced" one, matching the intention from Design. This also adds the relevant handling to ensure the device-level setting triggers an update immediately when changed. Fixes https://github.com/vector-im/riot-web/issues/14304 --- src/components/structures/LoggedInView.tsx | 17 ++++++++++++----- .../tabs/user/AppearanceUserSettingsTab.tsx | 1 - src/settings/Settings.js | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 9c01480df2..9fbc98dee3 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -146,6 +146,7 @@ class LoggedInView extends React.Component { protected readonly _resizeContainer: React.RefObject; protected readonly _sessionStore: sessionStore; protected readonly _sessionStoreToken: { remove: () => void }; + protected readonly _compactLayoutWatcherRef: string; protected resizer: Resizer; constructor(props, context) { @@ -177,6 +178,10 @@ class LoggedInView extends React.Component { this._matrixClient.on("sync", this.onSync); this._matrixClient.on("RoomState.events", this.onRoomStateEvents); + this._compactLayoutWatcherRef = SettingsStore.watchSetting( + "useCompactLayout", null, this.onCompactLayoutChanged, + ); + fixupColorFonts(); this._roomView = React.createRef(); @@ -194,6 +199,7 @@ class LoggedInView extends React.Component { this._matrixClient.removeListener("accountData", this.onAccountData); this._matrixClient.removeListener("sync", this.onSync); this._matrixClient.removeListener("RoomState.events", this.onRoomStateEvents); + SettingsStore.unwatchSetting(this._compactLayoutWatcherRef); if (this._sessionStoreToken) { this._sessionStoreToken.remove(); } @@ -263,16 +269,17 @@ class LoggedInView extends React.Component { } onAccountData = (event) => { - if (event.getType() === "im.vector.web.settings") { - this.setState({ - useCompactLayout: event.getContent().useCompactLayout, - }); - } if (event.getType() === "m.ignored_user_list") { dis.dispatch({action: "ignore_state_changed"}); } }; + onCompactLayoutChanged = (setting, roomId, level, valueAtLevel, newValue) => { + this.setState({ + useCompactLayout: valueAtLevel, + }); + }; + onSync = (syncState, oldSyncState, data) => { const oldErrCode = ( this.state.syncErrorData && diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx index f02147608d..c577aed543 100644 --- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx @@ -291,7 +291,6 @@ export default class AppearanceUserSettingsTab extends React.Component {customThemeForm} - ); } diff --git a/src/settings/Settings.js b/src/settings/Settings.js index 218e151ef4..58d9ed4f31 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -202,7 +202,7 @@ export const SETTINGS = { default: false, }, "useCompactLayout": { - supportedLevels: LEVELS_ACCOUNT_SETTINGS, + supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, displayName: _td('Use a more compact ‘Modern’ layout'), default: false, }, From 8bb3f4a2252472653957fc3f08d78ae75d504bdc Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 3 Jul 2020 12:06:00 +0100 Subject: [PATCH 15/74] Remove duplicate compact settings, handle device level updates This removes the duplicate setting for compact layout from the appearance tab, and leaves the "advanced" one, matching the intention from Design. This also adds the relevant handling to ensure the device-level setting triggers an update immediately when changed. Fixes https://github.com/vector-im/riot-web/issues/14304 --- src/components/structures/LoggedInView.tsx | 17 ++++++++++++----- .../tabs/user/AppearanceUserSettingsTab.tsx | 1 - src/settings/Settings.js | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 9c01480df2..9fbc98dee3 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -146,6 +146,7 @@ class LoggedInView extends React.Component { protected readonly _resizeContainer: React.RefObject; protected readonly _sessionStore: sessionStore; protected readonly _sessionStoreToken: { remove: () => void }; + protected readonly _compactLayoutWatcherRef: string; protected resizer: Resizer; constructor(props, context) { @@ -177,6 +178,10 @@ class LoggedInView extends React.Component { this._matrixClient.on("sync", this.onSync); this._matrixClient.on("RoomState.events", this.onRoomStateEvents); + this._compactLayoutWatcherRef = SettingsStore.watchSetting( + "useCompactLayout", null, this.onCompactLayoutChanged, + ); + fixupColorFonts(); this._roomView = React.createRef(); @@ -194,6 +199,7 @@ class LoggedInView extends React.Component { this._matrixClient.removeListener("accountData", this.onAccountData); this._matrixClient.removeListener("sync", this.onSync); this._matrixClient.removeListener("RoomState.events", this.onRoomStateEvents); + SettingsStore.unwatchSetting(this._compactLayoutWatcherRef); if (this._sessionStoreToken) { this._sessionStoreToken.remove(); } @@ -263,16 +269,17 @@ class LoggedInView extends React.Component { } onAccountData = (event) => { - if (event.getType() === "im.vector.web.settings") { - this.setState({ - useCompactLayout: event.getContent().useCompactLayout, - }); - } if (event.getType() === "m.ignored_user_list") { dis.dispatch({action: "ignore_state_changed"}); } }; + onCompactLayoutChanged = (setting, roomId, level, valueAtLevel, newValue) => { + this.setState({ + useCompactLayout: valueAtLevel, + }); + }; + onSync = (syncState, oldSyncState, data) => { const oldErrCode = ( this.state.syncErrorData && diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx index f02147608d..c577aed543 100644 --- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx @@ -291,7 +291,6 @@ export default class AppearanceUserSettingsTab extends React.Component {customThemeForm} - ); } diff --git a/src/settings/Settings.js b/src/settings/Settings.js index 820329f6c6..f0c7840426 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -202,7 +202,7 @@ export const SETTINGS = { default: false, }, "useCompactLayout": { - supportedLevels: LEVELS_ACCOUNT_SETTINGS, + supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, displayName: _td('Use a more compact ‘Modern’ layout'), default: false, }, From 452bb73dcf26e3620ac3c6778faec7528b678b21 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Fri, 3 Jul 2020 13:22:03 +0100 Subject: [PATCH 16/74] Upgrade matrix-js-sdk to 7.1.0 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index b64784d3e3..8ad654683c 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "is-ip": "^2.0.0", "linkifyjs": "^2.1.6", "lodash": "^4.17.14", - "matrix-js-sdk": "7.1.0-rc.1", + "matrix-js-sdk": "7.1.0", "minimist": "^1.2.0", "pako": "^1.0.5", "parse5": "^5.1.1", diff --git a/yarn.lock b/yarn.lock index ff73c6e6e0..7b9b3f1e04 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5820,10 +5820,10 @@ mathml-tag-names@^2.0.1: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -matrix-js-sdk@7.1.0-rc.1: - version "7.1.0-rc.1" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-7.1.0-rc.1.tgz#eba6232c5980010783f0d4987421e36d4b365ead" - integrity sha512-h0KmA5RcY7Ui2406Et2plrwQRk8R6r3dBdsihaE6XIbtFaqQgJ4Q/eECFphHU20HA2SXrqXVcgvAIfds5y27aw== +matrix-js-sdk@7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-7.1.0.tgz#b3e3304e890df45c827706831748935168ee839f" + integrity sha512-Y+MdOfsVQRGx0KcwSdNtwsFNGWUF7Zi7wPxUSa050J8eBlbkXUNFAyuSWviLJ5pUwzmp9XMEKD9Cdv+tGZG1ww== dependencies: "@babel/runtime" "^7.8.3" another-json "^0.2.0" From 24687589e1275ee512c5b499b2019076be4fa298 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Fri, 3 Jul 2020 13:27:47 +0100 Subject: [PATCH 17/74] Prepare changelog for v2.9.0 --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 948583937d..c545a83044 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +Changes in [2.9.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.9.0) (2020-07-03) +=================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.9.0-rc.1...v2.9.0) + + * Remove duplicate compact settings, handle device level updates + [\#4889](https://github.com/matrix-org/matrix-react-sdk/pull/4889) + Changes in [2.9.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.9.0-rc.1) (2020-07-01) ============================================================================================================= [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.8.1...v2.9.0-rc.1) From 21b7b88fde947fb95053b2726de56d17a8bfff4e Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Fri, 3 Jul 2020 13:27:48 +0100 Subject: [PATCH 18/74] v2.9.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8ad654683c..68957ab220 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "2.9.0-rc.1", + "version": "2.9.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From 5c56a55cf02101a7c214555684d92c746a0df5c7 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Fri, 3 Jul 2020 13:29:56 +0100 Subject: [PATCH 19/74] Reset matrix-js-sdk back to develop branch --- package.json | 2 +- yarn.lock | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 68957ab220..41ba3f47c1 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "is-ip": "^2.0.0", "linkifyjs": "^2.1.6", "lodash": "^4.17.14", - "matrix-js-sdk": "7.1.0", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", "minimist": "^1.2.0", "pako": "^1.0.5", "parse5": "^5.1.1", diff --git a/yarn.lock b/yarn.lock index 7b9b3f1e04..98b42a0b29 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5820,10 +5820,9 @@ mathml-tag-names@^2.0.1: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -matrix-js-sdk@7.1.0: +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": version "7.1.0" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-7.1.0.tgz#b3e3304e890df45c827706831748935168ee839f" - integrity sha512-Y+MdOfsVQRGx0KcwSdNtwsFNGWUF7Zi7wPxUSa050J8eBlbkXUNFAyuSWviLJ5pUwzmp9XMEKD9Cdv+tGZG1ww== + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/2a688bdac828dc62916437d83c72cef1e525d5f9" dependencies: "@babel/runtime" "^7.8.3" another-json "^0.2.0" From 511e0682fcd49fab9a508c7075b7100cefd4bcc4 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Fri, 3 Jul 2020 13:32:24 +0100 Subject: [PATCH 20/74] Add upgrade line to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c545a83044..b2e1f1af0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Changes in [2.9.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v =================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.9.0-rc.1...v2.9.0) + * Upgrade to JS SDK 7.1.0 * Remove duplicate compact settings, handle device level updates [\#4889](https://github.com/matrix-org/matrix-react-sdk/pull/4889) From 9b0c711837727c0134609c45793877aa4bf3cc25 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 3 Jul 2020 14:34:43 +0100 Subject: [PATCH 21/74] Make the UserMenu more accessible Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/UserMenu.tsx | 87 ++++++++++++++++---------- 1 file changed, 55 insertions(+), 32 deletions(-) diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index ff828e0da7..90a6a7d699 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -21,7 +21,7 @@ import { ActionPayload } from "../../dispatcher/payloads"; import { Action } from "../../dispatcher/actions"; import { createRef } from "react"; import { _t } from "../../languageHandler"; -import {ContextMenu, ContextMenuButton} from "./ContextMenu"; +import {ContextMenu, ContextMenuButton, MenuItem} from "./ContextMenu"; import {USER_NOTIFICATIONS_TAB, USER_SECURITY_TAB} from "../views/dialogs/UserSettingsDialog"; import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload"; import RedesignFeedbackDialog from "../views/dialogs/RedesignFeedbackDialog"; @@ -30,7 +30,7 @@ import LogoutDialog from "../views/dialogs/LogoutDialog"; import SettingsStore, {SettingLevel} from "../../settings/SettingsStore"; import {getCustomTheme} from "../../theme"; import {getHostingLink} from "../../utils/HostingLink"; -import AccessibleButton, {ButtonEvent} from "../views/elements/AccessibleButton"; +import {ButtonEvent} from "../views/elements/AccessibleButton"; import SdkConfig from "../../SdkConfig"; import {getHomePageUrl} from "../../utils/pages"; import { OwnProfileStore } from "../../stores/OwnProfileStore"; @@ -50,6 +50,19 @@ interface IState { isDarkTheme: boolean; } +interface IMenuButtonProps { + iconClassName: string; + label: string; + onClick(ev: ButtonEvent); +} + +const MenuButton: React.FC = ({iconClassName, label, onClick}) => { + return + + {label} + ; +}; + export default class UserMenu extends React.Component { private dispatcherRef: string; private themeWatcherRef: string; @@ -102,8 +115,11 @@ export default class UserMenu extends React.Component { private onAction = (ev: ActionPayload) => { if (ev.action !== Action.ToggleUserMenu) return; // not interested - // For accessibility - if (this.buttonRef.current) this.buttonRef.current.click(); + if (this.state.contextMenuPosition) { + this.setState({contextMenuPosition: null}); + } else { + if (this.buttonRef.current) this.buttonRef.current.click(); + } }; private onOpenMenuClick = (ev: InputEvent) => { @@ -206,10 +222,11 @@ export default class UserMenu extends React.Component { let homeButton = null; if (this.hasHomePage) { homeButton = ( - - - {_t("Home")} - + ); } @@ -246,32 +263,38 @@ export default class UserMenu extends React.Component { {hostingLink}
{homeButton} - this.onSettingsOpen(e, USER_NOTIFICATIONS_TAB)}> - - {_t("Notification settings")} - - this.onSettingsOpen(e, USER_SECURITY_TAB)}> - - {_t("Security & privacy")} - - this.onSettingsOpen(e, null)}> - - {_t("All settings")} - - - - {_t("Archived rooms")} - - - - {_t("Feedback")} - + this.onSettingsOpen(e, USER_NOTIFICATIONS_TAB)} + /> + this.onSettingsOpen(e, USER_SECURITY_TAB)} + /> + this.onSettingsOpen(e, null)} + /> + +
- - - {_t("Sign out")} - +
From 47ee00ec5da64235692971acd68dd3d9c14ed038 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 3 Jul 2020 14:43:02 +0100 Subject: [PATCH 22/74] Make explore button at all accessible Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/LeftPanel2.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index 0cf52039fe..f5a946f964 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -250,8 +250,8 @@ export default class LeftPanel2 extends React.Component { onVerticalArrow={this.onKeyDown} /> From c8a93e9dd708f2af4fb0d6aa4c4c93f12ed8ea8d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 3 Jul 2020 14:49:25 +0100 Subject: [PATCH 23/74] clean-up Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/LeftPanel2.tsx | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index f5a946f964..23a9e74646 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -166,20 +166,18 @@ export default class LeftPanel2 extends React.Component { switch (ev.key) { case Key.ARROW_UP: case Key.ARROW_DOWN: - this.onMoveFocus(ev, ev.key === Key.ARROW_UP); + ev.stopPropagation(); + ev.preventDefault(); + this.onMoveFocus(ev.key === Key.ARROW_UP); break; } }; - private onMoveFocus = (ev: React.KeyboardEvent, up: boolean) => { + private onMoveFocus = (up: boolean) => { let element = this.focusedElement; - // unclear why this isn't needed - // var descending = (up == this.focusDirection) ? this.focusDescending : !this.focusDescending; - // this.focusDirection = up; - let descending = false; // are we currently descending or ascending through the DOM tree? - let classes; + let classes: DOMTokenList; do { const child = up ? element.lastElementChild : element.firstElementChild; @@ -212,14 +210,8 @@ export default class LeftPanel2 extends React.Component { classes.contains("mx_RoomSearch_input"))); if (element) { - ev.stopPropagation(); - ev.preventDefault(); element.focus(); this.focusedElement = element; - } else { - // if navigation is via up/down arrow-keys, trap in the widget so it doesn't send to composer - ev.stopPropagation(); - ev.preventDefault(); } }; From 9c1efe728c6385180421fc42aad4d3e8eff4c122 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 3 Jul 2020 08:54:54 -0600 Subject: [PATCH 24/74] Revert "Remove a bunch of noisy logging from the room list" --- src/stores/room-list/RoomListStore2.ts | 10 ++++++++ src/stores/room-list/algorithms/Algorithm.ts | 23 +++++++++++++++++++ .../list-ordering/ImportanceAlgorithm.ts | 3 +++ .../list-ordering/NaturalAlgorithm.ts | 3 +++ .../filters/CommunityFilterCondition.ts | 2 ++ .../room-list/filters/NameFilterCondition.ts | 2 ++ 6 files changed, 43 insertions(+) diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index d4aec93035..e5205f6051 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -101,6 +101,8 @@ export class RoomListStore2 extends AsyncStore { console.warn(`${activeRoomId} is current in RVS but missing from client - clearing sticky room`); this.algorithm.stickyRoom = null; } else if (activeRoom !== this.algorithm.stickyRoom) { + // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 + console.log(`Changing sticky room to ${activeRoomId}`); this.algorithm.stickyRoom = activeRoom; } } @@ -297,6 +299,8 @@ export class RoomListStore2 extends AsyncStore { private async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise { const shouldUpdate = await this.algorithm.handleRoomUpdate(room, cause); if (shouldUpdate) { + // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 + console.log(`[DEBUG] Room "${room.name}" (${room.roomId}) triggered by ${cause} requires list update`); this.emit(LISTS_UPDATE_EVENT, this); } } @@ -363,6 +367,8 @@ export class RoomListStore2 extends AsyncStore { } private onAlgorithmListUpdated = () => { + // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 + console.log("Underlying algorithm has triggered a list update - refiring"); this.emit(LISTS_UPDATE_EVENT, this); }; @@ -402,6 +408,8 @@ export class RoomListStore2 extends AsyncStore { } public addFilter(filter: IFilterCondition): void { + // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 + console.log("Adding filter condition:", filter); this.filterConditions.push(filter); if (this.algorithm) { this.algorithm.addFilterCondition(filter); @@ -409,6 +417,8 @@ export class RoomListStore2 extends AsyncStore { } public removeFilter(filter: IFilterCondition): void { + // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 + console.log("Removing filter condition:", filter); const idx = this.filterConditions.indexOf(filter); if (idx >= 0) { this.filterConditions.splice(idx, 1); diff --git a/src/stores/room-list/algorithms/Algorithm.ts b/src/stores/room-list/algorithms/Algorithm.ts index 80ca4656af..36abf86975 100644 --- a/src/stores/room-list/algorithms/Algorithm.ts +++ b/src/stores/room-list/algorithms/Algorithm.ts @@ -272,6 +272,9 @@ export class Algorithm extends EventEmitter { } } newMap[tagId] = allowedRoomsInThisTag; + + // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 + console.log(`[DEBUG] ${newMap[tagId].length}/${rooms.length} rooms filtered into ${tagId}`); } const allowedRooms = Object.values(newMap).reduce((rv, v) => { rv.push(...v); return rv; }, []); @@ -307,6 +310,9 @@ export class Algorithm extends EventEmitter { if (filteredRooms.length > 0) { this.filteredRooms[tagId] = filteredRooms; } + + // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 + console.log(`[DEBUG] ${filteredRooms.length}/${rooms.length} rooms filtered into ${tagId}`); } protected tryInsertStickyRoomToFilterSet(rooms: Room[], tagId: TagID) { @@ -345,6 +351,8 @@ export class Algorithm extends EventEmitter { } if (!this._cachedStickyRooms || !updatedTag) { + // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 + console.log(`Generating clone of cached rooms for sticky room handling`); const stickiedTagMap: ITagMap = {}; for (const tagId of Object.keys(this.cachedRooms)) { stickiedTagMap[tagId] = this.cachedRooms[tagId].map(r => r); // shallow clone @@ -355,6 +363,8 @@ export class Algorithm extends EventEmitter { if (updatedTag) { // Update the tag indicated by the caller, if possible. This is mostly to ensure // our cache is up to date. + // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 + console.log(`Replacing cached sticky rooms for ${updatedTag}`); this._cachedStickyRooms[updatedTag] = this.cachedRooms[updatedTag].map(r => r); // shallow clone } @@ -363,6 +373,8 @@ export class Algorithm extends EventEmitter { // we might have updated from the cache is also our sticky room. const sticky = this._stickyRoom; if (!updatedTag || updatedTag === sticky.tag) { + // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 + console.log(`Inserting sticky room ${sticky.room.roomId} at position ${sticky.position} in ${sticky.tag}`); this._cachedStickyRooms[sticky.tag].splice(sticky.position, 0, sticky.room); } @@ -454,9 +466,13 @@ export class Algorithm extends EventEmitter { // Split out the easy rooms first (leave and invite) const memberships = splitRoomsByMembership(rooms); for (const room of memberships[EffectiveMembership.Invite]) { + // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 + console.log(`[DEBUG] "${room.name}" (${room.roomId}) is an Invite`); newTags[DefaultTagID.Invite].push(room); } for (const room of memberships[EffectiveMembership.Leave]) { + // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 + console.log(`[DEBUG] "${room.name}" (${room.roomId}) is Historical`); newTags[DefaultTagID.Archived].push(room); } @@ -467,7 +483,11 @@ export class Algorithm extends EventEmitter { let inTag = false; if (tags.length > 0) { for (const tag of tags) { + // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 + console.log(`[DEBUG] "${room.name}" (${room.roomId}) is tagged as ${tag}`); if (!isNullOrUndefined(newTags[tag])) { + // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 + console.log(`[DEBUG] "${room.name}" (${room.roomId}) is tagged with VALID tag ${tag}`); newTags[tag].push(room); inTag = true; } @@ -477,6 +497,9 @@ export class Algorithm extends EventEmitter { if (!inTag) { // TODO: Determine if DM and push there instead: https://github.com/vector-im/riot-web/issues/14236 newTags[DefaultTagID.Untagged].push(room); + + // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 + console.log(`[DEBUG] "${room.name}" (${room.roomId}) is Untagged`); } } diff --git a/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts b/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts index 71b6e89df3..e95f92f985 100644 --- a/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts +++ b/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts @@ -87,6 +87,9 @@ export class ImportanceAlgorithm extends OrderingAlgorithm { public constructor(tagId: TagID, initialSortingAlgorithm: SortAlgorithm) { super(tagId, initialSortingAlgorithm); + + // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 + console.log(`[RoomListDebug] Constructed an ImportanceAlgorithm for ${tagId}`); } // noinspection JSMethodCanBeStatic diff --git a/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts b/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts index 1a75d8cf06..f74329cb4d 100644 --- a/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts +++ b/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts @@ -28,6 +28,9 @@ export class NaturalAlgorithm extends OrderingAlgorithm { public constructor(tagId: TagID, initialSortingAlgorithm: SortAlgorithm) { super(tagId, initialSortingAlgorithm); + + // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 + console.log(`[RoomListDebug] Constructed a NaturalAlgorithm for ${tagId}`); } public async setRooms(rooms: Room[]): Promise { diff --git a/src/stores/room-list/filters/CommunityFilterCondition.ts b/src/stores/room-list/filters/CommunityFilterCondition.ts index 45e65fb4f4..9f7d8daaa3 100644 --- a/src/stores/room-list/filters/CommunityFilterCondition.ts +++ b/src/stores/room-list/filters/CommunityFilterCondition.ts @@ -52,6 +52,8 @@ export class CommunityFilterCondition extends EventEmitter implements IFilterCon const beforeRoomIds = this.roomIds; this.roomIds = (await GroupStore.getGroupRooms(this.community.groupId)).map(r => r.roomId); if (arrayHasDiff(beforeRoomIds, this.roomIds)) { + // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 + console.log("Updating filter for group: ", this.community.groupId); this.emit(FILTER_CHANGED); } }; diff --git a/src/stores/room-list/filters/NameFilterCondition.ts b/src/stores/room-list/filters/NameFilterCondition.ts index 6014a122f8..12f147990d 100644 --- a/src/stores/room-list/filters/NameFilterCondition.ts +++ b/src/stores/room-list/filters/NameFilterCondition.ts @@ -41,6 +41,8 @@ export class NameFilterCondition extends EventEmitter implements IFilterConditio public set search(val: string) { this._search = val; + // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 + console.log("Updating filter for room name search:", this._search); this.emit(FILTER_CHANGED); } From 3f62f20a8594aa233c9bcda365dbfd19a66f0783 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 3 Jul 2020 16:29:48 +0100 Subject: [PATCH 25/74] Fix theme selector bubbling out its click events and causing context menu to float away Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/UserMenu.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index ff828e0da7..b65e9f1cb6 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -130,7 +130,10 @@ export default class UserMenu extends React.Component { this.setState({contextMenuPosition: null}); }; - private onSwitchThemeClick = () => { + private onSwitchThemeClick = (ev: React.MouseEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + // Disable system theme matching if the user hits this button SettingsStore.setValue("use_system_theme", null, SettingLevel.DEVICE, false); From 5a542281ed49cc2f6d8b58485db5b65ad69eb165 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 3 Jul 2020 19:27:45 +0100 Subject: [PATCH 26/74] Make Styled Radio Button outlines default-off Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/views/elements/_StyledRadioButton.scss | 8 +++++--- .../dialogs/secretstorage/CreateSecretStorageDialog.js | 2 ++ src/components/views/elements/StyledRadioButton.tsx | 6 ++++-- src/components/views/elements/StyledRadioGroup.tsx | 4 +++- .../settings/tabs/user/AppearanceUserSettingsTab.tsx | 3 +++ 5 files changed, 17 insertions(+), 6 deletions(-) diff --git a/res/css/views/elements/_StyledRadioButton.scss b/res/css/views/elements/_StyledRadioButton.scss index 17a063593f..ffa1337ebb 100644 --- a/res/css/views/elements/_StyledRadioButton.scss +++ b/res/css/views/elements/_StyledRadioButton.scss @@ -28,9 +28,6 @@ limitations under the License. align-items: baseline; flex-grow: 1; - border: 1px solid $input-darker-bg-color; - border-radius: 8px; - > .mx_RadioButton_content { flex-grow: 1; @@ -110,6 +107,11 @@ limitations under the License. } } +.mx_RadioButton_outlined { + border: 1px solid $input-darker-bg-color; + border-radius: 8px; +} + .mx_RadioButton_checked { border-color: $accent-color; } diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index 984158c7a2..4cef817a38 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -444,6 +444,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { value={CREATE_STORAGE_OPTION_KEY} name="keyPassphrase" checked={this.state.passPhraseKeySelected === CREATE_STORAGE_OPTION_KEY} + outlined >
@@ -456,6 +457,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { value={CREATE_STORAGE_OPTION_PASSPHRASE} name="keyPassphrase" checked={this.state.passPhraseKeySelected === CREATE_STORAGE_OPTION_PASSPHRASE} + outlined >
diff --git a/src/components/views/elements/StyledRadioButton.tsx b/src/components/views/elements/StyledRadioButton.tsx index 3b83666048..2efd084861 100644 --- a/src/components/views/elements/StyledRadioButton.tsx +++ b/src/components/views/elements/StyledRadioButton.tsx @@ -18,6 +18,7 @@ import React from 'react'; import classnames from 'classnames'; interface IProps extends React.InputHTMLAttributes { + outlined?: boolean; } interface IState { @@ -29,7 +30,7 @@ export default class StyledRadioButton extends React.PureComponent {/* Used to render the radio button circle */} -
+
{children}
; diff --git a/src/components/views/elements/StyledRadioGroup.tsx b/src/components/views/elements/StyledRadioGroup.tsx index ded1342462..ea8f65d12b 100644 --- a/src/components/views/elements/StyledRadioGroup.tsx +++ b/src/components/views/elements/StyledRadioGroup.tsx @@ -32,10 +32,11 @@ interface IProps { className?: string; definitions: IDefinition[]; value?: T; // if not provided no options will be selected + outlined?: boolean; onChange(newValue: T); } -function StyledRadioGroup({name, definitions, value, className, onChange}: IProps) { +function StyledRadioGroup({name, definitions, value, className, outlined, onChange}: IProps) { const _onChange = e => { onChange(e.target.value); }; @@ -49,6 +50,7 @@ function StyledRadioGroup({name, definitions, value, className name={name} value={d.value} disabled={d.disabled} + outlined={outlined} > {d.label} diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx index f02147608d..4b2e09a3e3 100644 --- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx @@ -288,6 +288,7 @@ export default class AppearanceUserSettingsTab extends React.Component
{customThemeForm} @@ -355,6 +356,7 @@ export default class AppearanceUserSettingsTab extends React.Component {_t("Compact")} @@ -371,6 +373,7 @@ export default class AppearanceUserSettingsTab extends React.Component {_t("Modern")} From c8bb6f5904256b58b41fb0ba7a28e3503bf4ea4a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 3 Jul 2020 19:48:22 +0100 Subject: [PATCH 27/74] Improve radio outlines for message layout to be more consistent Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../tabs/user/_AppearanceUserSettingsTab.scss | 6 ++++++ .../tabs/user/AppearanceUserSettingsTab.tsx | 13 ++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss index d724b164e5..df766ab883 100644 --- a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss @@ -188,11 +188,17 @@ limitations under the License. .mx_RadioButton { flex-grow: 0; padding: 10px; + // create a horizontal separation line between the preview and the radio interaction + border-top: 1px solid $input-darker-bg-color; } .mx_EventTile_content { margin-right: 0; } + + &.mx_AppearanceUserSettingsTab_Layout_RadioButton_selected { + border-color: $accent-color; + } } .mx_RadioButton { diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx index 4b2e09a3e3..0a56fc3cb7 100644 --- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx @@ -34,6 +34,7 @@ import SettingsFlag from '../../../elements/SettingsFlag'; import Field from '../../../elements/Field'; import EventTilePreview from '../../../elements/EventTilePreview'; import StyledRadioGroup from "../../../elements/StyledRadioGroup"; +import classNames from 'classnames'; interface IProps { } @@ -344,8 +345,10 @@ export default class AppearanceUserSettingsTab extends React.Component {_t("Message layout")} -
-
+
+
{_t("Compact")}
-
+
{_t("Modern")} From af5f9b7c411cf893f155147700873a1e1eac3132 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 3 Jul 2020 19:53:06 +0100 Subject: [PATCH 28/74] revert dark mode separator colour Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../views/settings/tabs/user/_AppearanceUserSettingsTab.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss index df766ab883..94983a60bf 100644 --- a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss @@ -188,8 +188,6 @@ limitations under the License. .mx_RadioButton { flex-grow: 0; padding: 10px; - // create a horizontal separation line between the preview and the radio interaction - border-top: 1px solid $input-darker-bg-color; } .mx_EventTile_content { From afa71c7b7c5d3a5b4bce216450366edf95cbf2ad Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 3 Jul 2020 14:26:59 -0600 Subject: [PATCH 29/74] Fix minor issues with the badges in the new room list Fixes https://github.com/vector-im/riot-web/issues/14225 --- res/css/views/rooms/_RoomSublist2.scss | 2 +- res/css/views/rooms/_RoomTile2.scss | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss index 749e0451cd..0e76152f86 100644 --- a/res/css/views/rooms/_RoomSublist2.scss +++ b/res/css/views/rooms/_RoomSublist2.scss @@ -92,7 +92,7 @@ limitations under the License. // Apply the width and margin to the badge so the container doesn't occupy dead space .mx_NotificationBadge { - width: 16px; + // Do not set a width so the badges get properly sized margin-left: 8px; // same as menu+aux buttons } } diff --git a/res/css/views/rooms/_RoomTile2.scss b/res/css/views/rooms/_RoomTile2.scss index 1fd32d3555..7b606ab947 100644 --- a/res/css/views/rooms/_RoomTile2.scss +++ b/res/css/views/rooms/_RoomTile2.scss @@ -85,6 +85,7 @@ limitations under the License. height: 16px; // don't set width so that it takes no space when there is no badge to show margin: auto 0; // vertically align + position: relative; // fixes badge alignment in some scenarios // Create a flexbox to make aligning dot badges easier display: flex; From 4c7014167d19a55b858f4d7d61a959968347b4f1 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 5 Jul 2020 01:06:36 +0100 Subject: [PATCH 30/74] Improve a11y of default BaseAvatar Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/avatars/BaseAvatar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/avatars/BaseAvatar.js b/src/components/views/avatars/BaseAvatar.js index 508691e5fd..53e8d0072b 100644 --- a/src/components/views/avatars/BaseAvatar.js +++ b/src/components/views/avatars/BaseAvatar.js @@ -132,7 +132,7 @@ const BaseAvatar = (props) => { ); } else { return ( - + { textNode } { imgNode } From 1620feb55eb4419dd660439f2d91d4434a468c09 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 5 Jul 2020 01:07:46 +0100 Subject: [PATCH 31/74] Sprinkle in some better ARIA props Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/LeftPanel2.tsx | 9 ++++-- src/components/structures/RoomSearch.tsx | 7 +++-- src/components/views/rooms/RoomList2.tsx | 3 -- src/components/views/rooms/RoomSublist2.tsx | 22 +++++++++----- src/components/views/rooms/RoomTile2.tsx | 33 ++++++++++++++++++--- 5 files changed, 55 insertions(+), 19 deletions(-) diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index 23a9e74646..0b3fa2f7f6 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -235,7 +235,12 @@ export default class LeftPanel2 extends React.Component { private renderSearchExplore(): React.ReactNode { return ( -
+
{ // TODO fix the accessibility of this: https://github.com/vector-im/riot-web/issues/14180 className="mx_LeftPanel2_exploreButton" onClick={this.onExplore} - alt={_t("Explore rooms")} + title={_t("Explore rooms")} />
); diff --git a/src/components/structures/RoomSearch.tsx b/src/components/structures/RoomSearch.tsx index 7ed2acf276..15f3bd5b54 100644 --- a/src/components/structures/RoomSearch.tsx +++ b/src/components/structures/RoomSearch.tsx @@ -149,7 +149,8 @@ export default class RoomSearch extends React.PureComponent { let clearButton = ( ); @@ -157,8 +158,8 @@ export default class RoomSearch extends React.PureComponent { if (this.props.isMinimized) { icon = ( ); diff --git a/src/components/views/rooms/RoomList2.tsx b/src/components/views/rooms/RoomList2.tsx index b0bb70c9a0..06708931a2 100644 --- a/src/components/views/rooms/RoomList2.tsx +++ b/src/components/views/rooms/RoomList2.tsx @@ -276,9 +276,6 @@ export default class RoomList2 extends React.Component { className="mx_RoomList2" role="tree" aria-label={_t("Rooms")} - // Firefox sometimes makes this element focusable due to - // overflow:scroll;, so force it out of tab order. - tabIndex={-1} >{sublists}
)} diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index 21e7c581f0..69125ca88f 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -63,7 +63,7 @@ interface IProps { onAddRoom?: () => void; addRoomLabel: string; isInvite: boolean; - layout: ListLayout; + layout?: ListLayout; isMinimized: boolean; tagId: TagID; @@ -203,6 +203,7 @@ export default class RoomSublist2 extends React.Component { dis.dispatch({ action: 'view_room', room_id: room.roomId, + show_room_tile: true, // to make sure the room gets scrolled into view }); } }; @@ -383,16 +384,22 @@ export default class RoomSublist2 extends React.Component { private renderHeader(): React.ReactElement { return ( - + {({onFocus, isActive, ref}) => { const tabIndex = isActive ? 0 : -1; + let ariaLabel = _t("Jump to first unread room."); + if (this.props.tagId === DefaultTagID.Invite) { + ariaLabel = _t("Jump to first invite."); + } + const badge = ( ); @@ -433,7 +440,7 @@ export default class RoomSublist2 extends React.Component { // doesn't become sticky. // The same applies to the notification badge. return ( -
+
{ tabIndex={tabIndex} className="mx_RoomSublist2_headerText" role="treeitem" + aria-expanded={!this.props.layout || !this.props.layout.isCollapsed} aria-level={1} onClick={this.onHeaderClick} onContextMenu={this.onContextMenu} @@ -496,12 +504,12 @@ export default class RoomSublist2 extends React.Component { ); if (this.props.isMinimized) showMoreText = null; showNButton = ( -
+ {/* set by CSS masking */} {showMoreText} -
+
); } else if (this.numTiles <= visibleTiles.length && this.numTiles > this.props.layout.defaultVisibleTiles) { // we have all tiles visible - add a button to show less @@ -512,12 +520,12 @@ export default class RoomSublist2 extends React.Component { ); if (this.props.isMinimized) showLessText = null; showNButton = ( -
+ {/* set by CSS masking */} {showLessText} -
+ ); } diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index 8a9712b5a4..46b6d57501 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -30,9 +30,15 @@ import { ContextMenu, ContextMenuButton, MenuItemRadio } from "../../structures/ import { DefaultTagID, TagID } from "../../../stores/room-list/models"; import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore"; import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar"; -import { getRoomNotifsState, ALL_MESSAGES, ALL_MESSAGES_LOUD, MENTIONS_ONLY, MUTE } from "../../../RoomNotifs"; +import { + getRoomNotifsState, + setRoomNotifsState, + ALL_MESSAGES, + ALL_MESSAGES_LOUD, + MENTIONS_ONLY, + MUTE, +} from "../../../RoomNotifs"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; -import { setRoomNotifsState } from "../../../RoomNotifs"; import { TagSpecificNotificationState } from "../../../stores/notifications/TagSpecificNotificationState"; import { INotificationState } from "../../../stores/notifications/INotificationState"; import NotificationBadge from "./NotificationBadge"; @@ -406,10 +412,11 @@ export default class RoomTile2 extends React.Component { } } + const notificationColor = this.state.notificationState.color; const nameClasses = classNames({ "mx_RoomTile2_name": true, "mx_RoomTile2_nameWithPreview": !!messagePreview, - "mx_RoomTile2_nameHasUnreadEvents": this.state.notificationState.color >= NotificationColor.Bold, + "mx_RoomTile2_nameHasUnreadEvents": notificationColor >= NotificationColor.Bold, }); let nameContainer = ( @@ -422,6 +429,22 @@ export default class RoomTile2 extends React.Component { ); if (this.props.isMinimized) nameContainer = null; + let ariaLabel = name; + // The following labels are written in such a fashion to increase screen reader efficiency (speed). + if (this.props.tag === DefaultTagID.Invite) { + // append nothing + } else if (notificationColor >= NotificationColor.Red) { + ariaLabel += " " + _t("%(count)s unread messages including mentions.", { + count: this.state.notificationState.count, + }); + } else if (notificationColor >= NotificationColor.Grey) { + ariaLabel += " " + _t("%(count)s unread messages.", { + count: this.state.notificationState.count, + }); + } else if (notificationColor >= NotificationColor.Bold) { + ariaLabel += " " + _t("Unread messages."); + } + return ( @@ -434,8 +457,10 @@ export default class RoomTile2 extends React.Component { onMouseEnter={this.onTileMouseEnter} onMouseLeave={this.onTileMouseLeave} onClick={this.onTileClick} - role="treeitem" onContextMenu={this.onContextMenu} + role="treeitem" + aria-label={ariaLabel} + aria-selected={this.state.selected} > {roomAvatar} {nameContainer} From 4f1cd82b665e2d481cae53d3ae0a9f1e337aff8a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 5 Jul 2020 01:24:22 +0100 Subject: [PATCH 32/74] i18n Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/i18n/strings/en_EN.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b23264a297..3794816a27 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1208,6 +1208,8 @@ "Show": "Show", "Message preview": "Message preview", "List options": "List options", + "Jump to first unread room.": "Jump to first unread room.", + "Jump to first invite.": "Jump to first invite.", "Add room": "Add room", "Show %(count)s more|other": "Show %(count)s more", "Show %(count)s more|one": "Show %(count)s more", @@ -2089,6 +2091,8 @@ "Find a room…": "Find a room…", "Find a room… (e.g. %(exampleRoom)s)": "Find a room… (e.g. %(exampleRoom)s)", "If you can't find the room you're looking for, ask for an invite or Create a new room.": "If you can't find the room you're looking for, ask for an invite or Create a new room.", + "Clear filter": "Clear filter", + "Search rooms": "Search rooms", "You can't send any messages until you review and agree to our terms and conditions.": "You can't send any messages until you review and agree to our terms and conditions.", "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please contact your service administrator to continue using the service.": "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please contact your service administrator to continue using the service.", "Your message wasn't sent because this homeserver has exceeded a resource limit. Please contact your service administrator to continue using the service.": "Your message wasn't sent because this homeserver has exceeded a resource limit. Please contact your service administrator to continue using the service.", @@ -2100,8 +2104,6 @@ "Sent messages will be stored until your connection has returned.": "Sent messages will be stored until your connection has returned.", "Active call": "Active call", "There's no one else here! Would you like to invite others or stop warning about the empty room?": "There's no one else here! Would you like to invite others or stop warning about the empty room?", - "Jump to first unread room.": "Jump to first unread room.", - "Jump to first invite.": "Jump to first invite.", "You seem to be uploading files, are you sure you want to quit?": "You seem to be uploading files, are you sure you want to quit?", "You seem to be in a call, are you sure you want to quit?": "You seem to be in a call, are you sure you want to quit?", "Search failed": "Search failed", @@ -2116,7 +2118,6 @@ "Click to mute video": "Click to mute video", "Click to unmute audio": "Click to unmute audio", "Click to mute audio": "Click to mute audio", - "Clear filter": "Clear filter", "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.", "Tried to load a specific point in this room's timeline, but was unable to find it.": "Tried to load a specific point in this room's timeline, but was unable to find it.", "Failed to load timeline position": "Failed to load timeline position", From 83cfdd9c07e647ee7ed9c5f728e421771bf9c625 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 5 Jul 2020 01:30:22 +0100 Subject: [PATCH 33/74] Fix accessibility of the Explore button Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/structures/_LeftPanel2.scss | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/res/css/structures/_LeftPanel2.scss b/res/css/structures/_LeftPanel2.scss index bdaada0d15..a73658d916 100644 --- a/res/css/structures/_LeftPanel2.scss +++ b/res/css/structures/_LeftPanel2.scss @@ -86,11 +86,15 @@ $tagPanelWidth: 70px; // only applies in this file, used for calculations .mx_RoomSearch_expanded + .mx_LeftPanel2_exploreButton { // Cheaty way to return the occupied space to the filter input + flex-basis: 0; margin: 0; width: 0; - // Don't forget to hide the masked ::before icon - visibility: hidden; + // Don't forget to hide the masked ::before icon, + // using display:none or visibility:hidden would break accessibility + &::before { + content: none; + } } .mx_LeftPanel2_exploreButton { From 069cdf3ce01fa29181b53dc1d336bf699d7b709b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 5 Jul 2020 18:23:57 +0100 Subject: [PATCH 34/74] Fix room list v2 context menus to be aria menus Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/ContextMenu.js | 36 +++++++++++++++++++++ src/components/views/rooms/RoomSublist2.tsx | 26 ++++++++------- src/components/views/rooms/RoomTile2.tsx | 24 ++++++++++---- 3 files changed, 67 insertions(+), 19 deletions(-) diff --git a/src/components/structures/ContextMenu.js b/src/components/structures/ContextMenu.js index e43b0d1431..038208e49f 100644 --- a/src/components/structures/ContextMenu.js +++ b/src/components/structures/ContextMenu.js @@ -23,6 +23,8 @@ import classNames from 'classnames'; import {Key} from "../../Keyboard"; import * as sdk from "../../index"; import AccessibleButton from "../views/elements/AccessibleButton"; +import StyledCheckbox from "../views/elements/StyledCheckbox"; +import StyledRadioButton from "../views/elements/StyledRadioButton"; // Shamelessly ripped off Modal.js. There's probably a better way // of doing reusable widgets like dialog boxes & menus where we go and @@ -421,6 +423,23 @@ MenuItemCheckbox.propTypes = { onClick: PropTypes.func.isRequired, }; +// Semantic component for representing a styled role=menuitemcheckbox +export const StyledMenuItemCheckbox = ({children, label, active=false, disabled=false, ...props}) => { + return ( + + { children } + + ); +}; +StyledMenuItemCheckbox.propTypes = { + ...AccessibleButton.propTypes, + label: PropTypes.string, // optional + active: PropTypes.bool.isRequired, + disabled: PropTypes.bool, // optional + className: PropTypes.string, // optional + onClick: PropTypes.func.isRequired, +}; + // Semantic component for representing a role=menuitemradio export const MenuItemRadio = ({children, label, active=false, disabled=false, ...props}) => { const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); @@ -439,6 +458,23 @@ MenuItemRadio.propTypes = { onClick: PropTypes.func.isRequired, }; +// Semantic component for representing a styled role=menuitemradio +export const StyledMenuItemRadio = ({children, label, active=false, disabled=false, ...props}) => { + return ( + + { children } + + ); +}; +StyledMenuItemRadio.propTypes = { + ...StyledMenuItemRadio.propTypes, + label: PropTypes.string, // optional + active: PropTypes.bool.isRequired, + disabled: PropTypes.bool, // optional + className: PropTypes.string, // optional + onClick: PropTypes.func.isRequired, +}; + // Placement method for to position context menu to right of elementRect with chevronOffset export const toRightOf = (elementRect, chevronOffset=12) => { const left = elementRect.right + window.pageXOffset + 3; diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index 69125ca88f..1b1e2ed66d 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -26,16 +26,18 @@ import AccessibleButton from "../../views/elements/AccessibleButton"; import RoomTile2 from "./RoomTile2"; import { ResizableBox, ResizeCallbackData } from "react-resizable"; import { ListLayout } from "../../../stores/room-list/ListLayout"; -import { ContextMenu, ContextMenuButton } from "../../structures/ContextMenu"; -import StyledCheckbox from "../elements/StyledCheckbox"; -import StyledRadioButton from "../elements/StyledRadioButton"; +import { + ContextMenu, + ContextMenuButton, + StyledMenuItemCheckbox, + StyledMenuItemRadio, +} from "../../structures/ContextMenu"; import RoomListStore from "../../../stores/room-list/RoomListStore2"; import { ListAlgorithm, SortAlgorithm } from "../../../stores/room-list/algorithms/models"; import { DefaultTagID, TagID } from "../../../stores/room-list/models"; import dis from "../../../dispatcher/dispatcher"; import NotificationBadge from "./NotificationBadge"; import { ListNotificationState } from "../../../stores/notifications/ListNotificationState"; -import Tooltip from "../elements/Tooltip"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import { Key } from "../../../Keyboard"; @@ -329,40 +331,40 @@ export default class RoomSublist2 extends React.Component {
{_t("Sort by")}
- this.onTagSortChanged(SortAlgorithm.Recent)} checked={!isAlphabetical} name={`mx_${this.props.tagId}_sortBy`} > {_t("Activity")} - - + this.onTagSortChanged(SortAlgorithm.Alphabetic)} checked={isAlphabetical} name={`mx_${this.props.tagId}_sortBy`} > {_t("A-Z")} - +

{_t("Unread rooms")}
- {_t("Always show first")} - +

{_t("Show")}
- {_t("Message preview")} - +
diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index 46b6d57501..dbaed0d819 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -26,7 +26,13 @@ import dis from '../../../dispatcher/dispatcher'; import { Key } from "../../../Keyboard"; import ActiveRoomObserver from "../../../ActiveRoomObserver"; import { _t } from "../../../languageHandler"; -import { ContextMenu, ContextMenuButton, MenuItemRadio } from "../../structures/ContextMenu"; +import { + ContextMenu, + ContextMenuButton, + MenuItemRadio, + MenuItemCheckbox, + MenuItem, +} from "../../structures/ContextMenu"; import { DefaultTagID, TagID } from "../../../stores/room-list/models"; import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore"; import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar"; @@ -328,20 +334,24 @@ export default class RoomTile2 extends React.Component {
- this.onTagRoom(e, DefaultTagID.Favourite)}> + this.onTagRoom(e, DefaultTagID.Favourite)} + active={false} // TODO: https://github.com/vector-im/riot-web/issues/14283 + label={_t("Favourite")} + > {_t("Favourite")} - - + + {_t("Settings")} - +
- + {_t("Leave Room")} - +
From 3cebfc8072b5f84de38c205b5e9be52788d81673 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 5 Jul 2020 19:31:24 +0100 Subject: [PATCH 35/74] Fix StyledMenuItemCheckbox and StyledMenuItemRadio Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/ContextMenu.js | 28 +++++++++++++++++++----- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/components/structures/ContextMenu.js b/src/components/structures/ContextMenu.js index 038208e49f..c4825ca1da 100644 --- a/src/components/structures/ContextMenu.js +++ b/src/components/structures/ContextMenu.js @@ -424,9 +424,17 @@ MenuItemCheckbox.propTypes = { }; // Semantic component for representing a styled role=menuitemcheckbox -export const StyledMenuItemCheckbox = ({children, label, active=false, disabled=false, ...props}) => { +export const StyledMenuItemCheckbox = ({children, label, checked=false, disabled=false, ...props}) => { return ( - + { children } ); @@ -434,7 +442,7 @@ export const StyledMenuItemCheckbox = ({children, label, active=false, disabled= StyledMenuItemCheckbox.propTypes = { ...AccessibleButton.propTypes, label: PropTypes.string, // optional - active: PropTypes.bool.isRequired, + checked: PropTypes.bool.isRequired, disabled: PropTypes.bool, // optional className: PropTypes.string, // optional onClick: PropTypes.func.isRequired, @@ -459,9 +467,17 @@ MenuItemRadio.propTypes = { }; // Semantic component for representing a styled role=menuitemradio -export const StyledMenuItemRadio = ({children, label, active=false, disabled=false, ...props}) => { +export const StyledMenuItemRadio = ({children, label, checked=false, disabled=false, ...props}) => { return ( - + { children } ); @@ -469,7 +485,7 @@ export const StyledMenuItemRadio = ({children, label, active=false, disabled=fal StyledMenuItemRadio.propTypes = { ...StyledMenuItemRadio.propTypes, label: PropTypes.string, // optional - active: PropTypes.bool.isRequired, + checked: PropTypes.bool.isRequired, disabled: PropTypes.bool, // optional className: PropTypes.string, // optional onClick: PropTypes.func.isRequired, From a68e23c9e089a66894f85b8cdd24ae0cc2be54b4 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 5 Jul 2020 19:38:45 +0100 Subject: [PATCH 36/74] Make message previews accessible via describedby Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/RoomTile2.tsx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index dbaed0d819..3d9a4b5aca 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -80,6 +80,8 @@ interface IState { generalMenuPosition: PartialDOMRect; } +const messagePreviewId = (roomId: string) => `mx_RoomTile2_messagePreview_${roomId}`; + const contextMenuBelow = (elementRect: PartialDOMRect) => { // align the context menu's icons with the icon which opened the context menu const left = elementRect.left + window.pageXOffset - 9; @@ -135,6 +137,10 @@ export default class RoomTile2 extends React.Component { return !this.props.isMinimized && this.props.tag !== DefaultTagID.Invite; } + private get showMessagePreview(): boolean { + return !this.props.isMinimized && this.props.showMessagePreview; + } + public componentWillUnmount() { if (this.props.room) { ActiveRoomObserver.removeListener(this.props.room.roomId, this.onActiveRoomUpdate); @@ -408,14 +414,14 @@ export default class RoomTile2 extends React.Component { name = name.replace(":", ":\u200b"); // add a zero-width space to allow linewrapping after the colon let messagePreview = null; - if (this.props.showMessagePreview && !this.props.isMinimized) { + if (this.showMessagePreview) { // The preview store heavily caches this info, so should be safe to hammer. const text = MessagePreviewStore.instance.getPreviewForRoom(this.props.room, this.props.tag); // Only show the preview if there is one to show. if (text) { messagePreview = ( -
+
{text}
); @@ -455,6 +461,11 @@ export default class RoomTile2 extends React.Component { ariaLabel += " " + _t("Unread messages."); } + let ariaDescribedBy: string; + if (this.showMessagePreview) { + ariaDescribedBy = messagePreviewId(this.props.room.roomId); + } + return ( @@ -471,6 +482,7 @@ export default class RoomTile2 extends React.Component { role="treeitem" aria-label={ariaLabel} aria-selected={this.state.selected} + aria-describedby={ariaDescribedBy} > {roomAvatar} {nameContainer} From 7c29a53ebdca7cc06dbd51e34565371c9b04561e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 5 Jul 2020 19:59:29 +0100 Subject: [PATCH 37/74] aria-hide the notifications badge on room tiles as we have manual labels here Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/RoomTile2.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index 3d9a4b5aca..f3b22c3a64 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -397,8 +397,9 @@ export default class RoomTile2 extends React.Component { let badge: React.ReactNode; if (!this.props.isMinimized) { + // aria-hidden because we summarise the unread count/highlight status in a manual aria-label below badge = ( -
+