From b0be99a8f09626fc44ddf5b5c74bcbfda3e6cc3e Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Fri, 22 May 2020 14:41:25 -0500 Subject: [PATCH 001/294] Show timestamp of redaction on hover Signed-off-by: Aaron Raimist --- src/components/views/messages/RedactedBody.tsx | 8 +++++++- src/i18n/strings/en_EN.json | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/views/messages/RedactedBody.tsx b/src/components/views/messages/RedactedBody.tsx index 5dada64b52..5f80460d03 100644 --- a/src/components/views/messages/RedactedBody.tsx +++ b/src/components/views/messages/RedactedBody.tsx @@ -19,6 +19,8 @@ import {MatrixClient} from "matrix-js-sdk/src/client"; import {MatrixEvent} from "matrix-js-sdk/src/models/event"; import { _t } from "../../../languageHandler"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; +import {formatFullDate} from "../../../DateUtils"; +import SettingsStore from "../../../settings/SettingsStore"; interface IProps { mxEvent: MatrixEvent; @@ -36,8 +38,12 @@ const RedactedBody = React.forwardRef(({mxEvent}, ref) => { text = _t("Message deleted by %(name)s", { name: sender ? sender.name : redactedBecauseUserId }); } + const showTwelveHour = SettingsStore.getValue("showTwelveHourTimestamps"); + const fullDate = formatFullDate(new Date(unsigned.redacted_because.origin_server_ts), showTwelveHour); + const titleText = _t("Message deleted on %(date)s", { date: fullDate }); + return ( - + { text } ); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f79d93b98f..a20acd9015 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1352,6 +1352,7 @@ "reacted with %(shortName)s": "reacted with %(shortName)s", "Message deleted": "Message deleted", "Message deleted by %(name)s": "Message deleted by %(name)s", + "Message deleted on %(date)s": "Message deleted on %(date)s", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s changed the avatar for %(roomName)s", "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removed the room avatar.", "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s changed the room avatar to ", From edb6bbc6c05eeb52a069beaf1845d1ad409941ad Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Mon, 15 Jun 2020 15:33:52 +0100 Subject: [PATCH 002/294] Implement font selection --- .../tabs/user/_AppearanceUserSettingsTab.scss | 14 ++++- .../tabs/user/AppearanceUserSettingsTab.tsx | 53 +++++++++++++++++++ src/dispatcher/actions.ts | 10 ++++ .../payloads/UpdateFontSizePayload.ts | 27 ++++++++++ .../payloads/UpdateSystemFontPayload.ts | 32 +++++++++++ src/i18n/strings/en_EN.json | 2 + src/settings/Settings.js | 14 +++++ ...izeController.js => FontSizeController.ts} | 6 ++- .../controllers/SystemFontController.ts | 36 +++++++++++++ .../controllers/UseSystemFontController.ts | 36 +++++++++++++ src/settings/watchers/FontWatcher.ts | 14 ++++- 11 files changed, 239 insertions(+), 5 deletions(-) create mode 100644 src/dispatcher/payloads/UpdateFontSizePayload.ts create mode 100644 src/dispatcher/payloads/UpdateSystemFontPayload.ts rename src/settings/controllers/{FontSizeController.js => FontSizeController.ts} (80%) create mode 100644 src/settings/controllers/SystemFontController.ts create mode 100644 src/settings/controllers/UseSystemFontController.ts diff --git a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss index 0756e98782..311a6b7c41 100644 --- a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss @@ -15,8 +15,7 @@ limitations under the License. */ .mx_AppearanceUserSettingsTab_fontSlider, -.mx_AppearanceUserSettingsTab_themeSection .mx_Field, -.mx_AppearanceUserSettingsTab_fontScaling .mx_Field { +.mx_AppearanceUserSettingsTab .mx_Field { @mixin mx_Settings_fullWidthField; } @@ -124,3 +123,14 @@ limitations under the License. .mx_SettingsTab_customFontSizeField { margin-left: calc($font-16px + 10px); } + +.mx_AppearanceUserSettingsTab_Advanced { + .mx_AppearanceUserSettingsTab_AdvancedToggle { + color: $accent-color; + margin-bottom: 16px; + } + + .mx_AppearanceUserSettingsTab_systemFont { + margin-left: calc($font-16px + 10px); + } +} diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx index fe575c2819..fa464526d3 100644 --- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx @@ -52,6 +52,9 @@ interface IState extends IThemeState { customThemeUrl: string, customThemeMessage: CustomThemeMessage, useCustomFontSize: boolean, + useSystemFont: boolean, + systemFont: string, + showAdvanced: boolean, } export default class AppearanceUserSettingsTab extends React.Component { @@ -67,6 +70,9 @@ export default class AppearanceUserSettingsTab extends React.Component {_t("Font size")}
@@ -314,6 +323,49 @@ export default class AppearanceUserSettingsTab extends React.Component; } + private renderAdvancedSection() { + const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag"); + const Field = sdk.getComponent("views.elements.Field"); + + const toggle =
this.setState({showAdvanced: !this.state.showAdvanced})} + > + {this.state.showAdvanced ? "Hide advanced" : "Show advanced"} +
; + + let advanced: React.ReactNode; + + if (this.state.showAdvanced) { + advanced =
+ this.setState({useSystemFont: checked})} + /> + { + this.setState({ + systemFont: value.target.value, + }) + + SettingsStore.setValue("systemFont", null, SettingLevel.DEVICE, value.target.value); + }} + tooltipContent="Set the name of a font installed on your system & Riot will attempt to use it." + disabled={!this.state.useSystemFont} + value={this.state.systemFont} + /> +
; + } + return
+ {toggle} + {advanced} +
+ } + render() { return (
@@ -323,6 +375,7 @@ export default class AppearanceUserSettingsTab extends React.Component {this.renderThemeSection()} {SettingsStore.isFeatureEnabled("feature_font_scaling") ? this.renderFontSection() : null} + {this.renderAdvancedSection()}
); } diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index 5f7ca1293c..a03c731818 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -69,4 +69,14 @@ export enum Action { * Opens the user menu (previously known as the top left menu). No additional payload information required. */ ToggleUserMenu = "toggle_user_menu", + + /** + * Sets the apps root font size. Should be used with UpdateFontSizePayload + */ + UpdateFontSize = "update-font-size", + + /** + * Sets a system font. Should be used with UpdateSystemFontPayload + */ + UpdateSystemFont = "update-system-font", } diff --git a/src/dispatcher/payloads/UpdateFontSizePayload.ts b/src/dispatcher/payloads/UpdateFontSizePayload.ts new file mode 100644 index 0000000000..3eac3e4607 --- /dev/null +++ b/src/dispatcher/payloads/UpdateFontSizePayload.ts @@ -0,0 +1,27 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { ActionPayload } from "../payloads"; +import { Action } from "../actions"; + +export interface UpdateFontSizePayload extends ActionPayload { + action: Action.UpdateFontSize, + + /** + * The font size to set the root to + */ + size: number; +} diff --git a/src/dispatcher/payloads/UpdateSystemFontPayload.ts b/src/dispatcher/payloads/UpdateSystemFontPayload.ts new file mode 100644 index 0000000000..73475e10d5 --- /dev/null +++ b/src/dispatcher/payloads/UpdateSystemFontPayload.ts @@ -0,0 +1,32 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { ActionPayload } from "../payloads"; +import { Action } from "../actions"; + +export interface UpdateSystemFontPayload extends ActionPayload { + action: Action.UpdateSystemFont, + + /** + * Specify whether to use a system font or the stylesheet font + */ + useSystemFont: boolean; + + /** + * The system font to use + */ + font: string; +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 396c3f9111..c53b474b13 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -459,6 +459,8 @@ "Mirror local video feed": "Mirror local video feed", "Enable Community Filter Panel": "Enable Community Filter Panel", "Match system theme": "Match system theme", + "Use a system font": "Use a system font", + "System font name": "System font name", "Allow Peer-to-Peer for 1:1 calls": "Allow Peer-to-Peer for 1:1 calls", "Send analytics data": "Send analytics data", "Never send encrypted messages to unverified sessions from this session": "Never send encrypted messages to unverified sessions from this session", diff --git a/src/settings/Settings.js b/src/settings/Settings.js index fad932fa4b..44440c1722 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -30,6 +30,8 @@ import PushToMatrixClientController from './controllers/PushToMatrixClientContro import ReloadOnChangeController from "./controllers/ReloadOnChangeController"; import {RIGHT_PANEL_PHASES} from "../stores/RightPanelStorePhases"; import FontSizeController from './controllers/FontSizeController'; +import SystemFontController from './controllers/SystemFontController'; +import UseSystemFontController from './controllers/UseSystemFontController'; // These are just a bunch of helper arrays to avoid copy/pasting a bunch of times const LEVELS_ROOM_SETTINGS = ['device', 'room-device', 'room-account', 'account', 'config']; @@ -313,6 +315,18 @@ export const SETTINGS = { default: true, displayName: _td("Match system theme"), }, + "useSystemFont": { + supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, + default: false, + displayName: _td("Use a system font"), + controller: new UseSystemFontController(), + }, + "systemFont": { + supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, + default: "", + displayName: _td("System font name"), + controller: new SystemFontController(), + }, "webRtcAllowPeerToPeer": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG, displayName: _td('Allow Peer-to-Peer for 1:1 calls'), diff --git a/src/settings/controllers/FontSizeController.js b/src/settings/controllers/FontSizeController.ts similarity index 80% rename from src/settings/controllers/FontSizeController.js rename to src/settings/controllers/FontSizeController.ts index 3ef01ab99b..6440fd32fe 100644 --- a/src/settings/controllers/FontSizeController.js +++ b/src/settings/controllers/FontSizeController.ts @@ -16,6 +16,8 @@ limitations under the License. import SettingController from "./SettingController"; import dis from "../../dispatcher/dispatcher"; +import { UpdateFontSizePayload } from "../../dispatcher/payloads/UpdateFontSizePayload"; +import { Action } from "../../dispatcher/actions"; export default class FontSizeController extends SettingController { constructor() { @@ -24,8 +26,8 @@ export default class FontSizeController extends SettingController { onChange(level, roomId, newValue) { // Dispatch font size change so that everything open responds to the change. - dis.dispatch({ - action: "update-font-size", + dis.dispatch({ + action: Action.UpdateFontSize, size: newValue, }); } diff --git a/src/settings/controllers/SystemFontController.ts b/src/settings/controllers/SystemFontController.ts new file mode 100644 index 0000000000..4f591efc17 --- /dev/null +++ b/src/settings/controllers/SystemFontController.ts @@ -0,0 +1,36 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import SettingController from "./SettingController"; +import SettingsStore from "../SettingsStore"; +import dis from "../../dispatcher/dispatcher"; +import { UpdateSystemFontPayload } from "../../dispatcher/payloads/UpdateSystemFontPayload"; +import { Action } from "../../dispatcher/actions"; + +export default class SystemFontController extends SettingController { + constructor() { + super(); + } + + onChange(level, roomId, newValue) { + // Dispatch font size change so that everything open responds to the change. + dis.dispatch({ + action: Action.UpdateSystemFont, + useSystemFont: SettingsStore.getValue("useSystemFont"), + font: newValue, + }); + } +} diff --git a/src/settings/controllers/UseSystemFontController.ts b/src/settings/controllers/UseSystemFontController.ts new file mode 100644 index 0000000000..d598b25962 --- /dev/null +++ b/src/settings/controllers/UseSystemFontController.ts @@ -0,0 +1,36 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import SettingController from "./SettingController"; +import SettingsStore from "../SettingsStore"; +import dis from "../../dispatcher/dispatcher"; +import { UpdateSystemFontPayload } from "../../dispatcher/payloads/UpdateSystemFontPayload"; +import { Action } from "../../dispatcher/actions"; + +export default class UseSystemFontController extends SettingController { + constructor() { + super(); + } + + onChange(level, roomId, newValue) { + // Dispatch font size change so that everything open responds to the change. + dis.dispatch({ + action: Action.UpdateSystemFont, + useSystemFont: newValue, + font: SettingsStore.getValue("systemFont"), + }); + } +} diff --git a/src/settings/watchers/FontWatcher.ts b/src/settings/watchers/FontWatcher.ts index 5527284cd0..cc843edb4d 100644 --- a/src/settings/watchers/FontWatcher.ts +++ b/src/settings/watchers/FontWatcher.ts @@ -18,6 +18,8 @@ import dis from '../../dispatcher/dispatcher'; import SettingsStore, {SettingLevel} from '../SettingsStore'; import IWatcher from "./Watcher"; import { toPx } from '../../utils/units'; +import { Action } from '../../dispatcher/actions'; +import { UpdateSystemFontPayload } from '../../dispatcher/payloads/UpdateSystemFontPayload'; export class FontWatcher implements IWatcher { public static readonly MIN_SIZE = 8; @@ -33,6 +35,10 @@ export class FontWatcher implements IWatcher { public start() { this.setRootFontSize(SettingsStore.getValue("baseFontSize")); + this.setSystemFont({ + useSystemFont: SettingsStore.getValue("useSystemFont"), + font: SettingsStore.getValue("systemFont"), + }) this.dispatcherRef = dis.register(this.onAction); } @@ -41,8 +47,10 @@ export class FontWatcher implements IWatcher { } private onAction = (payload) => { - if (payload.action === 'update-font-size') { + if (payload.action === Action.UpdateFontSize) { this.setRootFontSize(payload.size); + } else if (payload.action === Action.UpdateSystemFont) { + this.setSystemFont(payload) } }; @@ -54,4 +62,8 @@ export class FontWatcher implements IWatcher { } (document.querySelector(":root")).style.fontSize = toPx(fontSize); }; + + private setSystemFont = ({useSystemFont, font}) => { + document.body.style.fontFamily = useSystemFont ? font : ""; + } } From aee9cd51a069f5553ea2ca7379f009519dde3424 Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Mon, 15 Jun 2020 16:34:56 +0100 Subject: [PATCH 003/294] Remove shadowed variable --- .../views/settings/tabs/user/AppearanceUserSettingsTab.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx index fa464526d3..6c70e89e28 100644 --- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx @@ -22,7 +22,6 @@ import SettingsStore, {SettingLevel} from "../../../../../settings/SettingsStore import * as sdk from "../../../../../index"; import { enumerateThemes } from "../../../../../theme"; import ThemeWatcher from "../../../../../settings/watchers/ThemeWatcher"; -import Field from "../../../elements/Field"; import Slider from "../../../elements/Slider"; import AccessibleButton from "../../../elements/AccessibleButton"; import dis from "../../../../../dispatcher/dispatcher"; From 02ccdcb802fecff01d103984f9e1765d3e8eace4 Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Mon, 15 Jun 2020 17:42:30 +0100 Subject: [PATCH 004/294] Fix field width and add tooltip --- .../settings/tabs/user/_AppearanceUserSettingsTab.scss | 7 +++++-- src/components/views/elements/Field.tsx | 8 +++++--- .../settings/tabs/user/AppearanceUserSettingsTab.tsx | 1 + 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss index 311a6b7c41..ce99b85d35 100644 --- a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss @@ -14,11 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_AppearanceUserSettingsTab_fontSlider, -.mx_AppearanceUserSettingsTab .mx_Field { +.mx_AppearanceUserSettingsTab_fontSlider { @mixin mx_Settings_fullWidthField; } +.mx_AppearanceUserSettingsTab .mx_Field { + width: 256px; +} + .mx_AppearanceUserSettingsTab_fontSlider { display: flex; flex-direction: row; diff --git a/src/components/views/elements/Field.tsx b/src/components/views/elements/Field.tsx index 771d2182ea..4d60550e02 100644 --- a/src/components/views/elements/Field.tsx +++ b/src/components/views/elements/Field.tsx @@ -60,6 +60,8 @@ interface IProps extends React.InputHTMLAttributes { const Tooltip = sdk.getComponent("elements.Tooltip"); let fieldTooltip; if (tooltipContent || this.state.feedback) { - const addlClassName = tooltipClassName ? tooltipClassName : ''; + const addClassName = tooltipClassName ? tooltipClassName : ''; fieldTooltip = ; } diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx index 6c70e89e28..3259c74ff4 100644 --- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx @@ -354,6 +354,7 @@ export default class AppearanceUserSettingsTab extends React.Component From 045217ee8db1ddc615c6bc7d674f1c123366efc4 Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Mon, 15 Jun 2020 17:46:16 +0100 Subject: [PATCH 005/294] fix style --- .../views/settings/tabs/user/_AppearanceUserSettingsTab.scss | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss index ce99b85d35..8877535d6c 100644 --- a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss @@ -14,15 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_AppearanceUserSettingsTab_fontSlider { - @mixin mx_Settings_fullWidthField; -} - .mx_AppearanceUserSettingsTab .mx_Field { width: 256px; } .mx_AppearanceUserSettingsTab_fontSlider { + @mixin mx_Settings_fullWidthField; display: flex; flex-direction: row; align-items: center; From dd23a50a3cccb50bfe4c90d0009f2a5951bbccdb Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 17 Jun 2020 21:40:30 +0100 Subject: [PATCH 006/294] Upgrade matrix-js-sdk to 7.0.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 5f9b7dde1f..98d7f7a6fa 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.0.0-rc.1", "minimist": "^1.2.0", "pako": "^1.0.5", "parse5": "^5.1.1", diff --git a/yarn.lock b/yarn.lock index d2d53692b5..2bb99f4602 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 "6.2.2" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/1c194e81637fb07fe6ad67cda33be0d5d4c10115" +matrix-js-sdk@7.0.0-rc.1: + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-7.0.0-rc.1.tgz#95a258218f5c5ec73ec4be510b28768c35809a0b" + integrity sha512-1znl0d2UxU6Mmimy+pMSQP1lQfsmDb9jxiKV5sfMvTBsLtUE2cTqEBVDNVoOHL4UJ9U4oMLsrBgu3sELkgSJLQ== dependencies: "@babel/runtime" "^7.8.3" another-json "^0.2.0" From 2533c33c54c870689960f4850e91e8b4be2c5e29 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 17 Jun 2020 21:46:33 +0100 Subject: [PATCH 007/294] Prepare changelog for v2.8.0-rc.1 --- CHANGELOG.md | 180 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 089bfa73e0..9d7a73b264 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,183 @@ +Changes in [2.8.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.8.0-rc.1) (2020-06-17) +============================================================================================================= +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.7.2...v2.8.0-rc.1) + + * Upgrade to JS SDK 7.0.0-rc.1 + * Fix Styled Checkbox and Radio Button disabled state + [\#4778](https://github.com/matrix-org/matrix-react-sdk/pull/4778) + * clean up and fix the isMasterRuleEnabled logic + [\#4782](https://github.com/matrix-org/matrix-react-sdk/pull/4782) + * Fix case-sensitivity of /me to match rest of slash commands + [\#4763](https://github.com/matrix-org/matrix-react-sdk/pull/4763) + * Add a 'show less' button to the new room list + [\#4765](https://github.com/matrix-org/matrix-react-sdk/pull/4765) + * Update from Weblate + [\#4781](https://github.com/matrix-org/matrix-react-sdk/pull/4781) + * Sticky and collapsing headers for new room list + [\#4758](https://github.com/matrix-org/matrix-react-sdk/pull/4758) + * Make the room list labs setting reload on change + [\#4780](https://github.com/matrix-org/matrix-react-sdk/pull/4780) + * Handle/hide old rooms in the room list + [\#4767](https://github.com/matrix-org/matrix-react-sdk/pull/4767) + * Add some media queries to improve UI on mobile (#3991) + [\#4656](https://github.com/matrix-org/matrix-react-sdk/pull/4656) + * Match fuzzy filtering a bit more reliably in the new room list + [\#4769](https://github.com/matrix-org/matrix-react-sdk/pull/4769) + * Improve Field ts definitions some more + [\#4777](https://github.com/matrix-org/matrix-react-sdk/pull/4777) + * Fix alignment of checkboxes in new room list's context menu + [\#4776](https://github.com/matrix-org/matrix-react-sdk/pull/4776) + * Fix Field ts def, fix LocalEchoWrapper and NotificationsEnabledController + [\#4775](https://github.com/matrix-org/matrix-react-sdk/pull/4775) + * Add presence indicators and globes to new room list + [\#4774](https://github.com/matrix-org/matrix-react-sdk/pull/4774) + * Include the sticky room when filtering in the new room list + [\#4772](https://github.com/matrix-org/matrix-react-sdk/pull/4772) + * Add a home button to the new room list menu when available + [\#4771](https://github.com/matrix-org/matrix-react-sdk/pull/4771) + * use group layout for search results + [\#4764](https://github.com/matrix-org/matrix-react-sdk/pull/4764) + * Fix m.id.phone spec compliance + [\#4757](https://github.com/matrix-org/matrix-react-sdk/pull/4757) + * User Info default power levels for ban/kick/redact to 50 as per spec + [\#4759](https://github.com/matrix-org/matrix-react-sdk/pull/4759) + * Match new room list's text search to old room list + [\#4768](https://github.com/matrix-org/matrix-react-sdk/pull/4768) + * Fix ordering of recent rooms in the new room list + [\#4766](https://github.com/matrix-org/matrix-react-sdk/pull/4766) + * Change theme selector to use new styled radio buttons + [\#4731](https://github.com/matrix-org/matrix-react-sdk/pull/4731) + * Use recovery keys over passphrases + [\#4686](https://github.com/matrix-org/matrix-react-sdk/pull/4686) + * Update from Weblate + [\#4760](https://github.com/matrix-org/matrix-react-sdk/pull/4760) + * Initial dark theme support for new room list + [\#4756](https://github.com/matrix-org/matrix-react-sdk/pull/4756) + * Support per-list options and algorithms on the new room list + [\#4754](https://github.com/matrix-org/matrix-react-sdk/pull/4754) + * Send read marker updates immediately after moving visually + [\#4755](https://github.com/matrix-org/matrix-react-sdk/pull/4755) + * Add a minimized view to the new room list + [\#4753](https://github.com/matrix-org/matrix-react-sdk/pull/4753) + * Fix e2e icon alignment in irc-layout + [\#4752](https://github.com/matrix-org/matrix-react-sdk/pull/4752) + * Add some resource leak protection to new room list badges + [\#4750](https://github.com/matrix-org/matrix-react-sdk/pull/4750) + * Fix read-receipt alignment + [\#4747](https://github.com/matrix-org/matrix-react-sdk/pull/4747) + * Show message previews on the new room list tiles + [\#4751](https://github.com/matrix-org/matrix-react-sdk/pull/4751) + * Fix various layout concerns with the new room list + [\#4749](https://github.com/matrix-org/matrix-react-sdk/pull/4749) + * Prioritize text on the clipboard over file + [\#4748](https://github.com/matrix-org/matrix-react-sdk/pull/4748) + * Move Settings flag to ts + [\#4729](https://github.com/matrix-org/matrix-react-sdk/pull/4729) + * Add a context menu to rooms in the new room list + [\#4743](https://github.com/matrix-org/matrix-react-sdk/pull/4743) + * Add hover states and basic context menu to new room list + [\#4742](https://github.com/matrix-org/matrix-react-sdk/pull/4742) + * Update resize handle for new designs in new room list + [\#4741](https://github.com/matrix-org/matrix-react-sdk/pull/4741) + * Improve general stability in the new room list + [\#4740](https://github.com/matrix-org/matrix-react-sdk/pull/4740) + * Reimplement breadcrumbs for new room list + [\#4735](https://github.com/matrix-org/matrix-react-sdk/pull/4735) + * Add styled radio buttons + [\#4744](https://github.com/matrix-org/matrix-react-sdk/pull/4744) + * Hide checkbox tick on dark backgrounds + [\#4730](https://github.com/matrix-org/matrix-react-sdk/pull/4730) + * Make checkboxes a11y friendly + [\#4746](https://github.com/matrix-org/matrix-react-sdk/pull/4746) + * EventIndex: Store and restore the encryption info for encrypted events. + [\#4738](https://github.com/matrix-org/matrix-react-sdk/pull/4738) + * Use IDestroyable instead of IDisposable + [\#4739](https://github.com/matrix-org/matrix-react-sdk/pull/4739) + * Add/improve badge counts in new room list + [\#4734](https://github.com/matrix-org/matrix-react-sdk/pull/4734) + * Convert FormattingUtils to TypeScript and add badge utility function + [\#4732](https://github.com/matrix-org/matrix-react-sdk/pull/4732) + * Add filtering and exploring to the new room list + [\#4736](https://github.com/matrix-org/matrix-react-sdk/pull/4736) + * Support prioritized room list filters + [\#4737](https://github.com/matrix-org/matrix-react-sdk/pull/4737) + * Clean up font scaling appearance + [\#4733](https://github.com/matrix-org/matrix-react-sdk/pull/4733) + * Add user menu to new room list + [\#4722](https://github.com/matrix-org/matrix-react-sdk/pull/4722) + * New room list basic styling and layout + [\#4711](https://github.com/matrix-org/matrix-react-sdk/pull/4711) + * Fix read receipt overlap + [\#4727](https://github.com/matrix-org/matrix-react-sdk/pull/4727) + * Load correct default font size + [\#4726](https://github.com/matrix-org/matrix-react-sdk/pull/4726) + * send state of lowBandwidth in rageshakes + [\#4724](https://github.com/matrix-org/matrix-react-sdk/pull/4724) + * Change internal font size from from 15 to 10 + [\#4725](https://github.com/matrix-org/matrix-react-sdk/pull/4725) + * Upgrade deps + [\#4723](https://github.com/matrix-org/matrix-react-sdk/pull/4723) + * Ensure active Jitsi conference is closed on widget pop-out + [\#4444](https://github.com/matrix-org/matrix-react-sdk/pull/4444) + * Introduce sticky rooms to the new room list + [\#4720](https://github.com/matrix-org/matrix-react-sdk/pull/4720) + * Handle remaining cases for room updates in new room list + [\#4721](https://github.com/matrix-org/matrix-react-sdk/pull/4721) + * Allow searching the emoji picker using other emoji + [\#4719](https://github.com/matrix-org/matrix-react-sdk/pull/4719) + * New room list scrolling and resizing + [\#4697](https://github.com/matrix-org/matrix-react-sdk/pull/4697) + * Don't show FormatBar if composer is empty + [\#4696](https://github.com/matrix-org/matrix-react-sdk/pull/4696) + * Split the left panel into new and old for new room list designs + [\#4687](https://github.com/matrix-org/matrix-react-sdk/pull/4687) + * Fix compact layout regression + [\#4712](https://github.com/matrix-org/matrix-react-sdk/pull/4712) + * fix emoji in safari + [\#4710](https://github.com/matrix-org/matrix-react-sdk/pull/4710) + * Fix not being able to dismiss new login toasts + [\#4709](https://github.com/matrix-org/matrix-react-sdk/pull/4709) + * Fix exceptions from Tooltip + [\#4708](https://github.com/matrix-org/matrix-react-sdk/pull/4708) + * Stop removing variation selector from quick reactions + [\#4707](https://github.com/matrix-org/matrix-react-sdk/pull/4707) + * Tidy up continuation algorithm and make it work for hidden profile changes + [\#4704](https://github.com/matrix-org/matrix-react-sdk/pull/4704) + * Profile settings should never show a disambiguated display name + [\#4699](https://github.com/matrix-org/matrix-react-sdk/pull/4699) + * Prevent (double) 4S bootstrap from RestoreKeyBackupDialog + [\#4701](https://github.com/matrix-org/matrix-react-sdk/pull/4701) + * Stop checkbox styling bleeding through room address selector + [\#4691](https://github.com/matrix-org/matrix-react-sdk/pull/4691) + * Center HeaderButtons + [\#4695](https://github.com/matrix-org/matrix-react-sdk/pull/4695) + * Add .well-known option to control default e2ee behaviour + [\#4605](https://github.com/matrix-org/matrix-react-sdk/pull/4605) + * Add max-width to right and left panels + [\#4692](https://github.com/matrix-org/matrix-react-sdk/pull/4692) + * Fix login loop where the sso flow returns to `#/login` + [\#4685](https://github.com/matrix-org/matrix-react-sdk/pull/4685) + * Don't clear MAU toasts when a successful sync comes in + [\#4690](https://github.com/matrix-org/matrix-react-sdk/pull/4690) + * Add initial filtering support to new room list + [\#4681](https://github.com/matrix-org/matrix-react-sdk/pull/4681) + * Bubble up a decline-to-render of verification events to outside wrapper + [\#4664](https://github.com/matrix-org/matrix-react-sdk/pull/4664) + * upgrade to twemoji 13.0.0 + [\#4672](https://github.com/matrix-org/matrix-react-sdk/pull/4672) + * Apply FocusLock to ImageView to capture Escape handling + [\#4666](https://github.com/matrix-org/matrix-react-sdk/pull/4666) + * Fix the 'complete security' screen + [\#4689](https://github.com/matrix-org/matrix-react-sdk/pull/4689) + * add null-guard for Autocomplete containerRef + [\#4688](https://github.com/matrix-org/matrix-react-sdk/pull/4688) + * Remove legacy codepaths for Unknown Device Error (UDE/UDD) handling + [\#4660](https://github.com/matrix-org/matrix-react-sdk/pull/4660) + * Remove feature_cross_signing + [\#4655](https://github.com/matrix-org/matrix-react-sdk/pull/4655) + * Autocomplete: use scrollIntoView for auto-scroll to fix it + [\#4670](https://github.com/matrix-org/matrix-react-sdk/pull/4670) + Changes in [2.7.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.7.2) (2020-06-16) =================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.7.1...v2.7.2) From b5aa66015c38748f1bc59a386cff8a7979903e36 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 17 Jun 2020 21:46:34 +0100 Subject: [PATCH 008/294] v2.8.0-rc.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 98d7f7a6fa..06c4c43622 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "2.7.2", + "version": "2.8.0-rc.1", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From e4d824839ac782966fe61343707f45c1797f92b2 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 18 Jun 2020 09:35:11 +0100 Subject: [PATCH 009/294] Revert "Use recovery keys over passphrases" --- .../structures/auth/_CompleteSecurity.scss | 4 - .../_CreateSecretStorageDialog.scss | 35 +- src/CrossSigningManager.js | 20 +- .../CreateSecretStorageDialog.js | 464 +++++++++++++----- .../structures/auth/CompleteSecurity.js | 4 - .../structures/auth/SetupEncryptionBody.js | 153 +----- .../keybackup/RestoreKeyBackupDialog.js | 2 +- .../AccessSecretStorageDialog.js | 9 +- .../views/settings/CrossSigningPanel.js | 2 +- src/i18n/strings/en_EN.json | 47 +- src/stores/SetupEncryptionStore.js | 64 +-- test/end-to-end-tests/src/usecases/signup.js | 23 +- 12 files changed, 400 insertions(+), 427 deletions(-) diff --git a/res/css/structures/auth/_CompleteSecurity.scss b/res/css/structures/auth/_CompleteSecurity.scss index b0462db477..f742be70e4 100644 --- a/res/css/structures/auth/_CompleteSecurity.scss +++ b/res/css/structures/auth/_CompleteSecurity.scss @@ -98,7 +98,3 @@ limitations under the License. } } } - -.mx_CompleteSecurity_resetText { - padding-top: 20px; -} diff --git a/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss b/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss index 9f1d0f4998..63e5a3de09 100644 --- a/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss +++ b/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss @@ -73,42 +73,33 @@ limitations under the License. margin-left: 20px; } +.mx_CreateSecretStorageDialog_recoveryKeyHeader { + margin-bottom: 1em; +} + .mx_CreateSecretStorageDialog_recoveryKeyContainer { - width: 380px; - margin-left: auto; - margin-right: auto; + display: flex; } .mx_CreateSecretStorageDialog_recoveryKey { - font-weight: bold; - text-align: center; + width: 262px; padding: 20px; color: $info-plinth-fg-color; background-color: $info-plinth-bg-color; - border-radius: 6px; - word-spacing: 1em; - margin-bottom: 20px; + margin-right: 12px; } .mx_CreateSecretStorageDialog_recoveryKeyButtons { + flex: 1; display: flex; - justify-content: space-between; align-items: center; } .mx_CreateSecretStorageDialog_recoveryKeyButtons .mx_AccessibleButton { - width: 160px; - padding-left: 0px; - padding-right: 0px; + margin-right: 10px; +} + +.mx_CreateSecretStorageDialog_recoveryKeyButtons button { + flex: 1; white-space: nowrap; } - -.mx_CreateSecretStorageDialog_continueSpinner { - margin-top: 33px; - text-align: right; -} - -.mx_CreateSecretStorageDialog_continueSpinner img { - width: 20px; - height: 20px; -} diff --git a/src/CrossSigningManager.js b/src/CrossSigningManager.js index d40f820ac0..c37d0f8bf5 100644 --- a/src/CrossSigningManager.js +++ b/src/CrossSigningManager.js @@ -30,8 +30,6 @@ import {encodeBase64} from "matrix-js-sdk/src/crypto/olmlib"; // operation ends. let secretStorageKeys = {}; let secretStorageBeingAccessed = false; -// Stores the 'passphraseOnly' option for the active storage access operation -let passphraseOnlyOption = null; function isCachingAllowed() { return ( @@ -101,7 +99,6 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) { const key = await inputToKey(input); return await MatrixClientPeg.get().checkSecretStorageKey(key, info); }, - passphraseOnly: passphraseOnlyOption, }, /* className= */ null, /* isPriorityModal= */ false, @@ -216,27 +213,19 @@ export async function promptForBackupPassphrase() { * * @param {Function} [func] An operation to perform once secret storage has been * bootstrapped. Optional. - * @param {object} [opts] Named options - * @param {bool} [opts.forceReset] Reset secret storage even if it's already set up - * @param {object} [opts.withKeys] Map of key ID to key for SSSS keys that the client - * already has available. If a key is not supplied here, the user will be prompted. - * @param {bool} [opts.passphraseOnly] If true, do not prompt for recovery key or to reset keys + * @param {bool} [forceReset] Reset secret storage even if it's already set up */ -export async function accessSecretStorage( - func = async () => { }, opts = {}, -) { +export async function accessSecretStorage(func = async () => { }, forceReset = false) { const cli = MatrixClientPeg.get(); secretStorageBeingAccessed = true; - passphraseOnlyOption = opts.passphraseOnly; - secretStorageKeys = Object.assign({}, opts.withKeys || {}); try { - if (!await cli.hasSecretStorageKey() || opts.forceReset) { + if (!await cli.hasSecretStorageKey() || forceReset) { // This dialog calls bootstrap itself after guiding the user through // passphrase creation. const { finished } = Modal.createTrackedDialogAsync('Create Secret Storage dialog', '', import("./async-components/views/dialogs/secretstorage/CreateSecretStorageDialog"), { - force: opts.forceReset, + force: forceReset, }, null, /* priority = */ false, /* static = */ true, ); @@ -274,6 +263,5 @@ export async function accessSecretStorage( if (!isCachingAllowed()) { secretStorageKeys = {}; } - passphraseOnlyOption = null; } } diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index 192427d384..d7b79c2cfa 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -20,23 +20,25 @@ import PropTypes from 'prop-types'; import * as sdk from '../../../../index'; import {MatrixClientPeg} from '../../../../MatrixClientPeg'; import FileSaver from 'file-saver'; -import {_t} from '../../../../languageHandler'; +import {_t, _td} from '../../../../languageHandler'; import Modal from '../../../../Modal'; import { promptForBackupPassphrase } from '../../../../CrossSigningManager'; import {copyNode} from "../../../../utils/strings"; import {SSOAuthEntry} from "../../../../components/views/auth/InteractiveAuthEntryComponents"; -import AccessibleButton from "../../../../components/views/elements/AccessibleButton"; -import DialogButtons from "../../../../components/views/elements/DialogButtons"; -import InlineSpinner from "../../../../components/views/elements/InlineSpinner"; - +import PassphraseField from "../../../../components/views/auth/PassphraseField"; const PHASE_LOADING = 0; const PHASE_LOADERROR = 1; const PHASE_MIGRATE = 2; -const PHASE_INTRO = 3; -const PHASE_SHOWKEY = 4; -const PHASE_STORING = 5; -const PHASE_CONFIRM_SKIP = 6; +const PHASE_PASSPHRASE = 3; +const PHASE_PASSPHRASE_CONFIRM = 4; +const PHASE_SHOWKEY = 5; +const PHASE_KEEPITSAFE = 6; +const PHASE_STORING = 7; +const PHASE_DONE = 8; +const PHASE_CONFIRM_SKIP = 9; + +const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc. /* * Walks the user through the process of creating a passphrase to guard Secure @@ -63,32 +65,34 @@ export default class CreateSecretStorageDialog extends React.PureComponent { this.state = { phase: PHASE_LOADING, - downloaded: false, + passPhrase: '', + passPhraseValid: false, + passPhraseConfirm: '', copied: false, + downloaded: false, backupInfo: null, - backupInfoFetched: false, - backupInfoFetchError: null, backupSigStatus: null, // does the server offer a UI auth flow with just m.login.password - // for /keys/device_signing/upload? (If we have an account password, we - // assume that it can) + // for /keys/device_signing/upload? canUploadKeysWithPasswordOnly: null, - canUploadKeyCheckInProgress: false, accountPassword: props.accountPassword || "", accountPasswordCorrect: null, - // No toggle for this: if we really don't want one, remove it & just hard code true + // status of the key backup toggle switch useKeyBackup: true, }; - if (props.accountPassword) { - // If we have an account password, we assume we can upload keys with - // just a password (otherwise leave it as null so we poll to check) - this.state.canUploadKeysWithPasswordOnly = true; - } - this._passphraseField = createRef(); - this.loadData(); + this._fetchBackupInfo(); + if (this.state.accountPassword) { + // If we have an account password in memory, let's simplify and + // assume it means password auth is also supported for device + // signing key upload as well. This avoids hitting the server to + // test auth flows, which may be slow under high load. + this.state.canUploadKeysWithPasswordOnly = true; + } else { + this._queryKeyUploadAuth(); + } MatrixClientPeg.get().on('crypto.keyBackupStatus', this._onKeyBackupStatusChange); } @@ -105,11 +109,13 @@ export default class CreateSecretStorageDialog extends React.PureComponent { MatrixClientPeg.get().isCryptoEnabled() && await MatrixClientPeg.get().isKeyBackupTrusted(backupInfo) ); + const { force } = this.props; + const phase = (backupInfo && !force) ? PHASE_MIGRATE : PHASE_PASSPHRASE; + this.setState({ - backupInfoFetched: true, + phase, backupInfo, backupSigStatus, - backupInfoFetchError: null, }); return { @@ -117,25 +123,20 @@ export default class CreateSecretStorageDialog extends React.PureComponent { backupSigStatus, }; } catch (e) { - this.setState({backupInfoFetchError: e}); + this.setState({phase: PHASE_LOADERROR}); } } async _queryKeyUploadAuth() { try { - this.setState({canUploadKeyCheckInProgress: true}); await MatrixClientPeg.get().uploadDeviceSigningKeys(null, {}); // We should never get here: the server should always require // UI auth to upload device signing keys. If we do, we upload // no keys which would be a no-op. console.log("uploadDeviceSigningKeys unexpectedly succeeded without UI auth!"); - this.setState({canUploadKeyCheckInProgress: false}); } catch (error) { if (!error.data || !error.data.flows) { console.log("uploadDeviceSigningKeys advertised no flows!"); - this.setState({ - canUploadKeyCheckInProgress: false, - }); return; } const canUploadKeysWithPasswordOnly = error.data.flows.some(f => { @@ -143,18 +144,10 @@ export default class CreateSecretStorageDialog extends React.PureComponent { }); this.setState({ canUploadKeysWithPasswordOnly, - canUploadKeyCheckInProgress: false, }); } } - async _createRecoveryKey() { - this._recoveryKey = await MatrixClientPeg.get().createRecoveryKeyFromPassphrase(); - this.setState({ - phase: PHASE_SHOWKEY, - }); - } - _onKeyBackupStatusChange = () => { if (this.state.phase === PHASE_MIGRATE) this._fetchBackupInfo(); } @@ -163,6 +156,12 @@ export default class CreateSecretStorageDialog extends React.PureComponent { this._recoveryKeyNode = n; } + _onUseKeyBackupChange = (enabled) => { + this.setState({ + useKeyBackup: enabled, + }); + } + _onMigrateFormSubmit = (e) => { e.preventDefault(); if (this.state.backupSigStatus.usable) { @@ -172,15 +171,12 @@ export default class CreateSecretStorageDialog extends React.PureComponent { } } - _onIntroContinueClick = () => { - this._createRecoveryKey(); - } - _onCopyClick = () => { const successful = copyNode(this._recoveryKeyNode); if (successful) { this.setState({ copied: true, + phase: PHASE_KEEPITSAFE, }); } } @@ -190,8 +186,10 @@ export default class CreateSecretStorageDialog extends React.PureComponent { type: 'text/plain;charset=us-ascii', }); FileSaver.saveAs(blob, 'recovery-key.txt'); + this.setState({ downloaded: true, + phase: PHASE_KEEPITSAFE, }); } @@ -247,9 +245,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { _bootstrapSecretStorage = async () => { this.setState({ - // we use LOADING here rather than STORING as STORING still shows the 'show key' - // screen which is not relevant: LOADING is just a generic spinner. - phase: PHASE_LOADING, + phase: PHASE_STORING, error: null, }); @@ -290,7 +286,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent { }, }); } - this.props.onFinished(true); + this.setState({ + phase: PHASE_DONE, + }); } catch (e) { if (this.state.canUploadKeysWithPasswordOnly && e.httpStatus === 401 && e.data.flows) { this.setState({ @@ -309,6 +307,10 @@ export default class CreateSecretStorageDialog extends React.PureComponent { this.props.onFinished(false); } + _onDone = () => { + this.props.onFinished(true); + } + _restoreBackup = async () => { // It's possible we'll need the backup key later on for bootstrapping, // so let's stash it here, rather than prompting for it twice. @@ -335,41 +337,88 @@ export default class CreateSecretStorageDialog extends React.PureComponent { } } - _onShowKeyContinueClick = () => { - this._bootstrapSecretStorage(); - } - _onLoadRetryClick = () => { - this.loadData(); - } - - async loadData() { this.setState({phase: PHASE_LOADING}); - const proms = []; - - if (!this.state.backupInfoFetched) proms.push(this._fetchBackupInfo()); - if (this.state.canUploadKeysWithPasswordOnly === null) proms.push(this._queryKeyUploadAuth()); - - await Promise.all(proms); - if (this.state.canUploadKeysWithPasswordOnly === null || this.state.backupInfoFetchError) { - this.setState({phase: PHASE_LOADERROR}); - } else if (this.state.backupInfo && !this.props.force) { - this.setState({phase: PHASE_MIGRATE}); - } else { - this.setState({phase: PHASE_INTRO}); - } + this._fetchBackupInfo(); } _onSkipSetupClick = () => { this.setState({phase: PHASE_CONFIRM_SKIP}); } - _onGoBackClick = () => { - if (this.state.backupInfo && !this.props.force) { - this.setState({phase: PHASE_MIGRATE}); - } else { - this.setState({phase: PHASE_INTRO}); + _onSetUpClick = () => { + this.setState({phase: PHASE_PASSPHRASE}); + } + + _onSkipPassPhraseClick = async () => { + this._recoveryKey = + await MatrixClientPeg.get().createRecoveryKeyFromPassphrase(); + this.setState({ + copied: false, + downloaded: false, + phase: PHASE_SHOWKEY, + }); + } + + _onPassPhraseNextClick = async (e) => { + e.preventDefault(); + if (!this._passphraseField.current) return; // unmounting + + await this._passphraseField.current.validate({ allowEmpty: false }); + if (!this._passphraseField.current.state.valid) { + this._passphraseField.current.focus(); + this._passphraseField.current.validate({ allowEmpty: false, focused: true }); + return; } + + this.setState({phase: PHASE_PASSPHRASE_CONFIRM}); + }; + + _onPassPhraseConfirmNextClick = async (e) => { + e.preventDefault(); + + if (this.state.passPhrase !== this.state.passPhraseConfirm) return; + + this._recoveryKey = + await MatrixClientPeg.get().createRecoveryKeyFromPassphrase(this.state.passPhrase); + this.setState({ + copied: false, + downloaded: false, + phase: PHASE_SHOWKEY, + }); + } + + _onSetAgainClick = () => { + this.setState({ + passPhrase: '', + passPhraseValid: false, + passPhraseConfirm: '', + phase: PHASE_PASSPHRASE, + }); + } + + _onKeepItSafeBackClick = () => { + this.setState({ + phase: PHASE_SHOWKEY, + }); + } + + _onPassPhraseValidate = (result) => { + this.setState({ + passPhraseValid: result.valid, + }); + }; + + _onPassPhraseChange = (e) => { + this.setState({ + passPhrase: e.target.value, + }); + } + + _onPassPhraseConfirmChange = (e) => { + this.setState({ + passPhraseConfirm: e.target.value, + }); } _onAccountPasswordChange = (e) => { @@ -384,14 +433,12 @@ export default class CreateSecretStorageDialog extends React.PureComponent { // Once we're confident enough in this (and it's supported enough) we can do // it automatically. // https://github.com/vector-im/riot-web/issues/11696 + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const Field = sdk.getComponent('views.elements.Field'); let authPrompt; let nextCaption = _t("Next"); - if (!this.state.backupSigStatus.usable) { - authPrompt = null; - nextCaption = _t("Upload"); - } else if (this.state.canUploadKeysWithPasswordOnly && !this.props.accountPassword) { + if (this.state.canUploadKeysWithPasswordOnly) { authPrompt =
{_t("Enter your account password to confirm the upgrade:")}
; + } else if (!this.state.backupSigStatus.usable) { + authPrompt =
+
{_t("Restore your key backup to upgrade your encryption")}
+
; + nextCaption = _t("Restore"); } else { authPrompt =

{_t("You'll need to authenticate with the server to confirm the upgrade.")} @@ -411,9 +463,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent { return

{_t( - "Upgrade your Recovery Key to store encryption keys & secrets " + - "with your account data. If you lose access to this login you'll " + - "need it to unlock your data.", + "Upgrade this session to allow it to verify other sessions, " + + "granting them access to encrypted messages and marking them " + + "as trusted for other users.", )}

{authPrompt}
; } - _renderPhaseShowKey() { - let continueButton; - if (this.state.phase === PHASE_SHOWKEY) { - continueButton = +

{_t( + "Set a recovery passphrase to secure encrypted information and recover it if you log out. " + + "This should be different to your account password:", + )}

+ +
+ +
+ + + + ; - } else { - continueButton =
- -
; + disabled={!this.state.passPhraseValid} + > + +
+ +
+ {_t("Advanced")} + + {_t("Set up with a recovery key")} + +
+ ; + } + + _renderPhasePassPhraseConfirm() { + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + const Field = sdk.getComponent('views.elements.Field'); + + let matchText; + let changeText; + if (this.state.passPhraseConfirm === this.state.passPhrase) { + matchText = _t("That matches!"); + changeText = _t("Use a different passphrase?"); + } else if (!this.state.passPhrase.startsWith(this.state.passPhraseConfirm)) { + // only tell them they're wrong if they've actually gone wrong. + // Security concious readers will note that if you left riot-web unattended + // on this screen, this would make it easy for a malicious person to guess + // your passphrase one letter at a time, but they could get this faster by + // just opening the browser's developer tools and reading it. + // Note that not having typed anything at all will not hit this clause and + // fall through so empty box === no hint. + matchText = _t("That doesn't match."); + changeText = _t("Go back to set it again."); } + let passPhraseMatch = null; + if (matchText) { + passPhraseMatch =
+
{matchText}
+
+ + {changeText} + +
+
; + } + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); + return
+

{_t( + "Enter your recovery passphrase a second time to confirm it.", + )}

+
+ +
+ {passPhraseMatch} +
+
+ + + +
; + } + + _renderPhaseShowKey() { + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); return

{_t( - "Store your Recovery Key somewhere safe, it can be used to unlock your encrypted messages & data.", + "Your recovery key is a safety net - you can use it to restore " + + "access to your encrypted messages if you forget your recovery passphrase.", + )}

+

{_t( + "Keep a copy of it somewhere secure, like a password manager or even a safe.", )}

+
+ {_t("Your recovery key")} +
{this._recoveryKey.encodedPrivateKey}
- - {_t("Download")} - - {_t("or")} - {this.state.copied ? _t("Copied!") : _t("Copy")} + {_t("Copy")} + + + {_t("Download")}
- {continueButton} +
; + } + + _renderPhaseKeepItSafe() { + let introText; + if (this.state.copied) { + introText = _t( + "Your recovery key has been copied to your clipboard, paste it to:", + {}, {b: s => {s}}, + ); + } else if (this.state.downloaded) { + introText = _t( + "Your recovery key is in your Downloads folder.", + {}, {b: s => {s}}, + ); + } + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); + return
+ {introText} +
    +
  • {_t("Print it and store it somewhere safe", {}, {b: s => {s}})}
  • +
  • {_t("Save it on a USB key or backup drive", {}, {b: s => {s}})}
  • +
  • {_t("Copy it to your personal cloud storage", {}, {b: s => {s}})}
  • +
+ + +
; } @@ -483,6 +671,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { } _renderPhaseLoadError() { + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); return

{_t("Unable to query secret storage status")}

@@ -495,44 +684,29 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
; } - _renderPhaseIntro() { - let cancelButton; - if (this.props.force) { - // if this is a forced key reset then aborting will just leave the old keys - // in place, and is thereforece just 'cancel' - cancelButton = ; - } else { - // if it's setting up from scratch then aborting leaves the user without - // crypto set up, so they skipping the setup. - cancelButton = ; - } - + _renderPhaseDone() { + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); return

{_t( - "Create a Recovery Key to store encryption keys & secrets with your account data. " + - "If you lose access to this login you’ll need it to unlock your data.", + "You can now verify your other devices, " + + "and other users to keep your chats safe.", )}

-
- - {cancelButton} - -
+
; } _renderPhaseSkipConfirm() { + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); return
{_t( "Without completing security on this session, it won’t have " + "access to encrypted messages.", )} @@ -542,15 +716,21 @@ export default class CreateSecretStorageDialog extends React.PureComponent { _titleForPhase(phase) { switch (phase) { - case PHASE_INTRO: - return _t('Create a Recovery Key'); case PHASE_MIGRATE: - return _t('Upgrade your Recovery Key'); + return _t('Upgrade your encryption'); + case PHASE_PASSPHRASE: + return _t('Set up encryption'); + case PHASE_PASSPHRASE_CONFIRM: + return _t('Confirm recovery passphrase'); case PHASE_CONFIRM_SKIP: return _t('Are you sure?'); case PHASE_SHOWKEY: + case PHASE_KEEPITSAFE: + return _t('Make a copy of your recovery key'); case PHASE_STORING: - return _t('Store your Recovery Key'); + return _t('Setting up keys'); + case PHASE_DONE: + return _t("You're done!"); default: return ''; } @@ -561,6 +741,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { let content; if (this.state.error) { + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); content =

{_t("Unable to set up secret storage")}

@@ -579,16 +760,27 @@ export default class CreateSecretStorageDialog extends React.PureComponent { case PHASE_LOADERROR: content = this._renderPhaseLoadError(); break; - case PHASE_INTRO: - content = this._renderPhaseIntro(); - break; case PHASE_MIGRATE: content = this._renderPhaseMigrate(); break; + case PHASE_PASSPHRASE: + content = this._renderPhasePassPhrase(); + break; + case PHASE_PASSPHRASE_CONFIRM: + content = this._renderPhasePassPhraseConfirm(); + break; case PHASE_SHOWKEY: - case PHASE_STORING: content = this._renderPhaseShowKey(); break; + case PHASE_KEEPITSAFE: + content = this._renderPhaseKeepItSafe(); + break; + case PHASE_STORING: + content = this._renderBusyPhase(); + break; + case PHASE_DONE: + content = this._renderPhaseDone(); + break; case PHASE_CONFIRM_SKIP: content = this._renderPhaseSkipConfirm(); break; @@ -605,7 +797,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { onFinished={this.props.onFinished} title={this._titleForPhase(this.state.phase)} headerImage={headerImage} - hasCancel={this.props.hasCancel} + hasCancel={this.props.hasCancel && [PHASE_PASSPHRASE].includes(this.state.phase)} fixedWidth={false} >
diff --git a/src/components/structures/auth/CompleteSecurity.js b/src/components/structures/auth/CompleteSecurity.js index e38ecd3eac..c73691611d 100644 --- a/src/components/structures/auth/CompleteSecurity.js +++ b/src/components/structures/auth/CompleteSecurity.js @@ -21,7 +21,6 @@ import * as sdk from '../../../index'; import { SetupEncryptionStore, PHASE_INTRO, - PHASE_RECOVERY_KEY, PHASE_BUSY, PHASE_DONE, PHASE_CONFIRM_SKIP, @@ -62,9 +61,6 @@ export default class CompleteSecurity extends React.Component { if (phase === PHASE_INTRO) { icon = ; title = _t("Verify this login"); - } else if (phase === PHASE_RECOVERY_KEY) { - icon = ; - title = _t("Recovery Key"); } else if (phase === PHASE_DONE) { icon = ; title = _t("Session verified"); diff --git a/src/components/structures/auth/SetupEncryptionBody.js b/src/components/structures/auth/SetupEncryptionBody.js index 7886ed26dd..26534c6e02 100644 --- a/src/components/structures/auth/SetupEncryptionBody.js +++ b/src/components/structures/auth/SetupEncryptionBody.js @@ -19,26 +19,15 @@ import PropTypes from 'prop-types'; import { _t } from '../../../languageHandler'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import * as sdk from '../../../index'; -import withValidation from '../../views/elements/Validation'; -import { decodeRecoveryKey } from 'matrix-js-sdk/src/crypto/recoverykey'; import { SetupEncryptionStore, PHASE_INTRO, - PHASE_RECOVERY_KEY, PHASE_BUSY, PHASE_DONE, PHASE_CONFIRM_SKIP, PHASE_FINISHED, } from '../../../stores/SetupEncryptionStore'; -function keyHasPassphrase(keyInfo) { - return ( - keyInfo.passphrase && - keyInfo.passphrase.salt && - keyInfo.passphrase.iterations - ); -} - export default class SetupEncryptionBody extends React.Component { static propTypes = { onFinished: PropTypes.func.isRequired, @@ -56,11 +45,6 @@ export default class SetupEncryptionBody extends React.Component { // Because of the latter, it lives in the state. verificationRequest: store.verificationRequest, backupInfo: store.backupInfo, - recoveryKey: '', - // whether the recovery key is a valid recovery key - recoveryKeyValid: null, - // whether the recovery key is the correct key or not - recoveryKeyCorrect: null, }; } @@ -83,19 +67,9 @@ export default class SetupEncryptionBody extends React.Component { store.stop(); } - _onResetClick = () => { + _onUsePassphraseClick = async () => { const store = SetupEncryptionStore.sharedInstance(); - store.startKeyReset(); - } - - _onUseRecoveryKeyClick = async () => { - const store = SetupEncryptionStore.sharedInstance(); - store.useRecoveryKey(); - } - - _onRecoveryKeyCancelClick() { - const store = SetupEncryptionStore.sharedInstance(); - store.cancelUseRecoveryKey(); + store.usePassPhrase(); } onSkipClick = () => { @@ -118,66 +92,6 @@ export default class SetupEncryptionBody extends React.Component { store.done(); } - _onUsePassphraseClick = () => { - const store = SetupEncryptionStore.sharedInstance(); - store.usePassPhrase(); - } - - _onRecoveryKeyChange = (e) => { - this.setState({recoveryKey: e.target.value}); - } - - _onRecoveryKeyValidate = async (fieldState) => { - const result = await this._validateRecoveryKey(fieldState); - this.setState({recoveryKeyValid: result.valid}); - return result; - } - - _validateRecoveryKey = withValidation({ - rules: [ - { - key: "required", - test: async (state) => { - try { - const decodedKey = decodeRecoveryKey(state.value); - const correct = await MatrixClientPeg.get().checkSecretStorageKey( - decodedKey, SetupEncryptionStore.sharedInstance().keyInfo, - ); - this.setState({ - recoveryKeyValid: true, - recoveryKeyCorrect: correct, - }); - return correct; - } catch (e) { - this.setState({ - recoveryKeyValid: false, - recoveryKeyCorrect: false, - }); - return false; - } - }, - invalid: function() { - if (this.state.recoveryKeyValid) { - return _t("This isn't the recovery key for your account"); - } else { - return _t("This isn't a valid recovery key"); - } - }, - valid: function() { - return _t("Looks good!"); - }, - }, - ], - }) - - _onRecoveryKeyFormSubmit = (e) => { - e.preventDefault(); - if (!this.state.recoveryKeyCorrect) return; - - const store = SetupEncryptionStore.sharedInstance(); - store.setupWithRecoveryKey(decodeRecoveryKey(this.state.recoveryKey)); - } - render() { const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); @@ -194,13 +108,6 @@ export default class SetupEncryptionBody extends React.Component { member={MatrixClientPeg.get().getUser(this.state.verificationRequest.otherUserId)} />; } else if (phase === PHASE_INTRO) { - const store = SetupEncryptionStore.sharedInstance(); - let recoveryKeyPrompt; - if (keyHasPassphrase(store.keyInfo)) { - recoveryKeyPrompt = _t("Use Recovery Key or Passphrase"); - } else { - recoveryKeyPrompt = _t("Use Recovery Key"); - } return (

{_t( @@ -224,67 +131,15 @@ export default class SetupEncryptionBody extends React.Component {

- - {recoveryKeyPrompt} + + {_t("Use Recovery Passphrase or Key")} {_t("Skip")}
-
{_t( - "If you've forgotten your recovery key you can " + - "", {}, { - button: sub => - {sub} - , - }, - )}
); - } else if (phase === PHASE_RECOVERY_KEY) { - const store = SetupEncryptionStore.sharedInstance(); - let keyPrompt; - if (keyHasPassphrase(store.keyInfo)) { - keyPrompt = _t( - "Enter your Recovery Key or enter a Recovery Passphrase to continue.", {}, - { - a: sub => {sub}, - }, - ); - } else { - keyPrompt = _t("Enter your Recovery Key to continue."); - } - - const Field = sdk.getComponent('elements.Field'); - return
-

{keyPrompt}

-
- -
-
- - {_t("Cancel")} - - - {_t("Continue")} - -
-
; } else if (phase === PHASE_DONE) { let message; if (this.state.backupInfo) { diff --git a/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js b/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js index 87ba6f7396..dd34dfbbf0 100644 --- a/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js +++ b/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js @@ -88,7 +88,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent { _onResetRecoveryClick = () => { this.props.onFinished(false); - accessSecretStorage(() => {}, {forceReset: true}); + accessSecretStorage(() => {}, /* forceReset = */ true); } _onRecoveryKeyChange = (e) => { diff --git a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js index 43697f8ee7..e2ceadfbb9 100644 --- a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js +++ b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js @@ -32,9 +32,6 @@ export default class AccessSecretStorageDialog extends React.PureComponent { keyInfo: PropTypes.object.isRequired, // Function from one of { passphrase, recoveryKey } -> boolean checkPrivateKey: PropTypes.func.isRequired, - // If true, only prompt for a passphrase and do not offer to restore with - // a recovery key or reset keys. - passphraseOnly: PropTypes.bool, } constructor(props) { @@ -61,7 +58,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent { _onResetRecoveryClick = () => { // Re-enter the access flow, but resetting storage this time around. this.props.onFinished(false); - accessSecretStorage(() => {}, {forceReset: true}); + accessSecretStorage(() => {}, /* forceReset = */ true); } _onRecoveryKeyChange = (e) => { @@ -167,7 +164,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent { primaryDisabled={this.state.passPhrase.length === 0} /> - {this.props.passphraseOnly ? null : _t( + {_t( "If you've forgotten your recovery passphrase you can "+ "use your recovery key or " + "set up new recovery options." @@ -237,7 +234,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent { primaryDisabled={!this.state.recoveryKeyValid} /> - {this.props.passphraseOnly ? null : _t( + {_t( "If you've forgotten your recovery key you can "+ "." , {}, { diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index f48ee3cd0d..7eb239cbca 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -113,7 +113,7 @@ export default class CrossSigningPanel extends React.PureComponent { _bootstrapSecureSecretStorage = async (forceReset=false) => { this.setState({ error: null }); try { - await accessSecretStorage(() => undefined, {forceReset}); + await accessSecretStorage(() => undefined, forceReset); } catch (e) { this.setState({ error: e }); console.error("Error bootstrapping secret storage", e); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 5de33ada55..9a41517664 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2068,7 +2068,6 @@ "Account settings": "Account settings", "Could not load user profile": "Could not load user profile", "Verify this login": "Verify this login", - "Recovery Key": "Recovery Key", "Session verified": "Session verified", "Failed to send email": "Failed to send email", "The email address linked to your account must be entered.": "The email address linked to your account must be entered.", @@ -2122,16 +2121,10 @@ "You can now close this window or log in to your new account.": "You can now close this window or log in to your new account.", "Registration Successful": "Registration Successful", "Create your account": "Create your account", - "This isn't the recovery key for your account": "This isn't the recovery key for your account", - "This isn't a valid recovery key": "This isn't a valid recovery key", - "Looks good!": "Looks good!", - "Use Recovery Key or Passphrase": "Use Recovery Key or Passphrase", - "Use Recovery Key": "Use Recovery Key", "Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.": "Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.", "This requires the latest Riot on your other devices:": "This requires the latest Riot on your other devices:", "or another cross-signing capable Matrix client": "or another cross-signing capable Matrix client", - "Enter your Recovery Key or enter a Recovery Passphrase to continue.": "Enter your Recovery Key or enter a Recovery Passphrase to continue.", - "Enter your Recovery Key to continue.": "Enter your Recovery Key to continue.", + "Use Recovery Passphrase or Key": "Use Recovery Passphrase or Key", "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.", "Your new session is now verified. Other users will see it as trusted.": "Your new session is now verified. Other users will see it as trusted.", "Without completing security on this session, it won’t have access to encrypted messages.": "Without completing security on this session, it won’t have access to encrypted messages.", @@ -2175,43 +2168,47 @@ "Confirm encryption setup": "Confirm encryption setup", "Click the button below to confirm setting up encryption.": "Click the button below to confirm setting up encryption.", "Enter your account password to confirm the upgrade:": "Enter your account password to confirm the upgrade:", + "Restore your key backup to upgrade your encryption": "Restore your key backup to upgrade your encryption", + "Restore": "Restore", "You'll need to authenticate with the server to confirm the upgrade.": "You'll need to authenticate with the server to confirm the upgrade.", - "Upgrade your Recovery Key to store encryption keys & secrets with your account data. If you lose access to this login you'll need it to unlock your data.": "Upgrade your Recovery Key to store encryption keys & secrets with your account data. If you lose access to this login you'll need it to unlock your data.", - "Store your Recovery Key somewhere safe, it can be used to unlock your encrypted messages & data.": "Store your Recovery Key somewhere safe, it can be used to unlock your encrypted messages & data.", - "Download": "Download", - "Copy": "Copy", - "Unable to query secret storage status": "Unable to query secret storage status", - "Retry": "Retry", - "Create a Recovery Key to store encryption keys & secrets with your account data. If you lose access to this login you’ll need it to unlock your data.": "Create a Recovery Key to store encryption keys & secrets with your account data. If you lose access to this login you’ll need it to unlock your data.", - "Create a Recovery Key": "Create a Recovery Key", - "Upgrade your Recovery Key": "Upgrade your Recovery Key", - "Store your Recovery Key": "Store your Recovery Key", - "Unable to set up secret storage": "Unable to set up secret storage", - "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.": "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.", - "For maximum security, this should be different from your account password.": "For maximum security, this should be different from your account password.", + "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.", + "Set a recovery passphrase to secure encrypted information and recover it if you log out. This should be different to your account password:": "Set a recovery passphrase to secure encrypted information and recover it if you log out. This should be different to your account password:", "Enter a recovery passphrase": "Enter a recovery passphrase", "Great! This recovery passphrase looks strong enough.": "Great! This recovery passphrase looks strong enough.", + "Back up encrypted message keys": "Back up encrypted message keys", "Set up with a recovery key": "Set up with a recovery key", "That matches!": "That matches!", "Use a different passphrase?": "Use a different passphrase?", "That doesn't match.": "That doesn't match.", "Go back to set it again.": "Go back to set it again.", - "Please enter your recovery passphrase a second time to confirm.": "Please enter your recovery passphrase a second time to confirm.", - "Repeat your recovery passphrase...": "Repeat your recovery passphrase...", + "Enter your recovery passphrase a second time to confirm it.": "Enter your recovery passphrase a second time to confirm it.", + "Confirm your recovery passphrase": "Confirm your recovery passphrase", "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.": "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.", "Keep a copy of it somewhere secure, like a password manager or even a safe.": "Keep a copy of it somewhere secure, like a password manager or even a safe.", "Your recovery key": "Your recovery key", + "Copy": "Copy", + "Download": "Download", "Your recovery key has been copied to your clipboard, paste it to:": "Your recovery key has been copied to your clipboard, paste it to:", "Your recovery key is in your Downloads folder.": "Your recovery key is in your Downloads folder.", "Print it and store it somewhere safe": "Print it and store it somewhere safe", "Save it on a USB key or backup drive": "Save it on a USB key or backup drive", "Copy it to your personal cloud storage": "Copy it to your personal cloud storage", + "Unable to query secret storage status": "Unable to query secret storage status", + "Retry": "Retry", + "You can now verify your other devices, and other users to keep your chats safe.": "You can now verify your other devices, and other users to keep your chats safe.", + "Upgrade your encryption": "Upgrade your encryption", + "Confirm recovery passphrase": "Confirm recovery passphrase", + "Make a copy of your recovery key": "Make a copy of your recovery key", + "You're done!": "You're done!", + "Unable to set up secret storage": "Unable to set up secret storage", + "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.": "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.", + "For maximum security, this should be different from your account password.": "For maximum security, this should be different from your account password.", + "Please enter your recovery passphrase a second time to confirm.": "Please enter your recovery passphrase a second time to confirm.", + "Repeat your recovery passphrase...": "Repeat your recovery passphrase...", "Your keys are being backed up (the first backup could take a few minutes).": "Your keys are being backed up (the first backup could take a few minutes).", "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another session.": "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another session.", "Set up Secure Message Recovery": "Set up Secure Message Recovery", "Secure your backup with a recovery passphrase": "Secure your backup with a recovery passphrase", - "Confirm your recovery passphrase": "Confirm your recovery passphrase", - "Make a copy of your recovery key": "Make a copy of your recovery key", "Starting backup...": "Starting backup...", "Success!": "Success!", "Create key backup": "Create key backup", diff --git a/src/stores/SetupEncryptionStore.js b/src/stores/SetupEncryptionStore.js index cc64e24a03..ae1f998b02 100644 --- a/src/stores/SetupEncryptionStore.js +++ b/src/stores/SetupEncryptionStore.js @@ -20,11 +20,10 @@ import { accessSecretStorage, AccessCancelledError } from '../CrossSigningManage import { PHASE_DONE as VERIF_PHASE_DONE } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; export const PHASE_INTRO = 0; -export const PHASE_RECOVERY_KEY = 1; -export const PHASE_BUSY = 2; -export const PHASE_DONE = 3; //final done stage, but still showing UX -export const PHASE_CONFIRM_SKIP = 4; -export const PHASE_FINISHED = 5; //UX can be closed +export const PHASE_BUSY = 1; +export const PHASE_DONE = 2; //final done stage, but still showing UX +export const PHASE_CONFIRM_SKIP = 3; +export const PHASE_FINISHED = 4; //UX can be closed export class SetupEncryptionStore extends EventEmitter { static sharedInstance() { @@ -37,19 +36,11 @@ export class SetupEncryptionStore extends EventEmitter { return; } this._started = true; - this.phase = PHASE_BUSY; + this.phase = PHASE_INTRO; this.verificationRequest = null; this.backupInfo = null; - - // ID of the key that the secrets we want are encrypted with - this.keyId = null; - // Descriptor of the key that the secrets we want are encrypted with - this.keyInfo = null; - MatrixClientPeg.get().on("crypto.verification.request", this.onVerificationRequest); MatrixClientPeg.get().on('userTrustStatusChanged', this._onUserTrustStatusChanged); - - this.fetchKeyInfo(); } stop() { @@ -66,49 +57,7 @@ export class SetupEncryptionStore extends EventEmitter { } } - async fetchKeyInfo() { - const keys = await MatrixClientPeg.get().isSecretStored('m.cross_signing.master', false); - if (Object.keys(keys).length === 0) { - this.keyId = null; - this.keyInfo = null; - } else { - // If the secret is stored under more than one key, we just pick an arbitrary one - this.keyId = Object.keys(keys)[0]; - this.keyInfo = keys[this.keyId]; - } - - this.phase = PHASE_INTRO; - this.emit("update"); - } - - async startKeyReset() { - try { - await accessSecretStorage(() => {}, {forceReset: true}); - // If the keys are reset, the trust status event will fire and we'll change state - } catch (e) { - // dialog was cancelled - stay on the current screen - } - } - - async useRecoveryKey() { - this.phase = PHASE_RECOVERY_KEY; - this.emit("update"); - } - - cancelUseRecoveryKey() { - this.phase = PHASE_INTRO; - this.emit("update"); - } - - async setupWithRecoveryKey(recoveryKey) { - this.startTrustCheck({[this.keyId]: recoveryKey}); - } - async usePassPhrase() { - this.startTrustCheck(); - } - - async startTrustCheck(withKeys) { this.phase = PHASE_BUSY; this.emit("update"); const cli = MatrixClientPeg.get(); @@ -135,9 +84,6 @@ export class SetupEncryptionStore extends EventEmitter { // to advance before this. await cli.restoreKeyBackupWithSecretStorage(backupInfo); } - }, { - withKeys, - passphraseOnly: true, }).catch(reject); } catch (e) { console.error(e); diff --git a/test/end-to-end-tests/src/usecases/signup.js b/test/end-to-end-tests/src/usecases/signup.js index 2859aadbda..aa9f6b7efa 100644 --- a/test/end-to-end-tests/src/usecases/signup.js +++ b/test/end-to-end-tests/src/usecases/signup.js @@ -79,7 +79,20 @@ module.exports = async function signup(session, username, password, homeserver) const acceptButton = await session.query('.mx_InteractiveAuthEntryComponents_termsSubmit'); await acceptButton.click(); - const xsignContButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary'); + //plow through cross-signing setup by entering arbitrary details + //TODO: It's probably important for the tests to know the passphrase + const xsigningPassphrase = 'a7eaXcjpa9!Yl7#V^h$B^%dovHUVX'; // https://xkcd.com/221/ + let passphraseField = await session.query('.mx_CreateSecretStorageDialog_passPhraseField input'); + await session.replaceInputText(passphraseField, xsigningPassphrase); + await session.delay(1000); // give it a second to analyze our passphrase for security + let xsignContButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary'); + await xsignContButton.click(); + + //repeat passphrase entry + passphraseField = await session.query('.mx_CreateSecretStorageDialog_passPhraseField input'); + await session.replaceInputText(passphraseField, xsigningPassphrase); + await session.delay(1000); // give it a second to analyze our passphrase for security + xsignContButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary'); await xsignContButton.click(); //ignore the recovery key @@ -88,11 +101,13 @@ module.exports = async function signup(session, username, password, homeserver) await copyButton.click(); //acknowledge that we copied the recovery key to a safe place - const copyContinueButton = await session.query( - '.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary', - ); + const copyContinueButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_primary'); await copyContinueButton.click(); + //acknowledge that we're done cross-signing setup and our keys are safe + const doneOkButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_primary'); + await doneOkButton.click(); + //wait for registration to finish so the hash gets set //onhashchange better? From c690cfc6c59dab6c366c17a284261a68035fb10f Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Thu, 18 Jun 2020 21:57:33 -0400 Subject: [PATCH 010/294] mark messages with a black shield if the megolm session isn't trusted --- res/css/views/rooms/_EventTile.scss | 5 +++ src/components/views/rooms/E2EIcon.js | 1 + src/components/views/rooms/EventTile.js | 51 ++++++++++++++++++------- src/i18n/strings/en_EN.json | 1 + 4 files changed, 45 insertions(+), 13 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 40a80f17bb..f6cfd9e1d1 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -354,6 +354,11 @@ limitations under the License. opacity: 1; } +.mx_EventTile_e2eIcon_unauthenticated { + background-image: url('$(res)/img/e2e/normal.svg'); + opacity: 1; +} + .mx_EventTile_e2eIcon_hidden { display: none; } diff --git a/src/components/views/rooms/E2EIcon.js b/src/components/views/rooms/E2EIcon.js index bf65c7fb7c..254e28dffa 100644 --- a/src/components/views/rooms/E2EIcon.js +++ b/src/components/views/rooms/E2EIcon.js @@ -28,6 +28,7 @@ export const E2E_STATE = { WARNING: "warning", UNKNOWN: "unknown", NORMAL: "normal", + UNAUTHENTICATED: "unauthenticated", }; const crossSigningUserTitles = { diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 7508cf3372..88c4ed2e7d 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -313,35 +313,52 @@ export default createReactClass({ return; } - // If we directly trust the device, short-circuit here - const verified = await this.context.isEventSenderVerified(mxEvent); - if (verified) { + const encryptionInfo = this.context.getEventEncryptionInfo(mxEvent); + const senderId = mxEvent.getSender(); + const userTrust = this.context.checkUserTrust(senderId); + + if (encryptionInfo.mismatchedSender) { + // something definitely wrong is going on here this.setState({ - verified: E2E_STATE.VERIFIED, - }, () => { - // Decryption may have caused a change in size - this.props.onHeightChanged(); - }); + verified: E2E_STATE.WARNING, + }, this.props.onHeightChanged); // Decryption may have caused a change in size return; } - if (!this.context.checkUserTrust(mxEvent.getSender()).isCrossSigningVerified()) { + if (!userTrust.isCrossSigningVerified()) { + // user is not verified, so default to everything is normal this.setState({ verified: E2E_STATE.NORMAL, - }, this.props.onHeightChanged); + }, this.props.onHeightChanged); // Decryption may have caused a change in size return; } - const eventSenderTrust = await this.context.checkEventSenderTrust(mxEvent); + const eventSenderTrust = this.context.checkDeviceTrust( + senderId, encryptionInfo.sender.deviceId, + ); if (!eventSenderTrust) { this.setState({ verified: E2E_STATE.UNKNOWN, - }, this.props.onHeightChanged); // Decryption may have cause a change in size + }, this.props.onHeightChanged); // Decryption may have caused a change in size + return; + } + + if (!eventSenderTrust.isVerified()) { + this.setState({ + verified: E2E_STATE.WARNING, + }, this.props.onHeightChanged); // Decryption may have caused a change in size + return; + } + + if (!encryptionInfo.authenticated) { + this.setState({ + verified: E2E_STATE.UNAUTHENTICATED, + }, this.props.onHeightChanged); // Decryption may have caused a change in size return; } this.setState({ - verified: eventSenderTrust.isVerified() ? E2E_STATE.VERIFIED : E2E_STATE.WARNING, + verified: E2E_STATE.VERIFIED, }, this.props.onHeightChanged); // Decryption may have caused a change in size }, @@ -526,6 +543,8 @@ export default createReactClass({ return; // no icon if we've not even cross-signed the user } else if (this.state.verified === E2E_STATE.VERIFIED) { return; // no icon for verified + } else if (this.state.verified === E2E_STATE.UNAUTHENTICATED) { + return (); } else if (this.state.verified === E2E_STATE.UNKNOWN) { return (); } else { @@ -976,6 +995,12 @@ function E2ePadlockUnknown(props) { ); } +function E2ePadlockUnauthenticated(props) { + return ( + + ); +} + class E2ePadlock extends React.Component { static propTypes = { icon: PropTypes.string.isRequired, diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 396c3f9111..2dcca91e82 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1023,6 +1023,7 @@ "Encrypted by an unverified session": "Encrypted by an unverified session", "Unencrypted": "Unencrypted", "Encrypted by a deleted session": "Encrypted by a deleted session", + "The authenticity of this encrypted message can't be guaranteed on this device.": "The authenticity of this encrypted message can't be guaranteed on this device.", "Please select the destination room for this message": "Please select the destination room for this message", "Invite only": "Invite only", "Scroll to most recent messages": "Scroll to most recent messages", From 3d7427cccafae58ebcae46c7f71c338b8db272aa Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Mon, 22 Jun 2020 11:39:11 +0100 Subject: [PATCH 011/294] Fix semicolons --- src/components/views/elements/Field.tsx | 4 ++-- .../tabs/user/AppearanceUserSettingsTab.tsx | 18 +++++++++--------- .../payloads/UpdateFontSizePayload.ts | 2 +- .../payloads/UpdateSystemFontPayload.ts | 2 +- src/settings/watchers/FontWatcher.ts | 6 +++--- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/components/views/elements/Field.tsx b/src/components/views/elements/Field.tsx index 9014e2c3c9..9d53576259 100644 --- a/src/components/views/elements/Field.tsx +++ b/src/components/views/elements/Field.tsx @@ -53,9 +53,9 @@ interface IProps { flagInvalid?: boolean; // If specified, contents will appear as a tooltip on the element and // validation feedback tooltips will be suppressed. - tooltipContent?: React.ReactNode, + tooltipContent?: React.ReactNode; // If specified the tooltip will be shown regardless of feedback - forceTooltipVisible?: boolean, + forceTooltipVisible?: boolean; // If specified alongside tooltipContent, the class name to apply to the // tooltip itself. tooltipClassName?: string; diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx index 10f5b4a5a4..5a08d99c19 100644 --- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx @@ -47,13 +47,13 @@ interface IState extends IThemeState { // String displaying the current selected fontSize. // Needs to be string for things like '17.' without // trailing 0s. - fontSize: string, - customThemeUrl: string, - customThemeMessage: CustomThemeMessage, - useCustomFontSize: boolean, - useSystemFont: boolean, - systemFont: string, - showAdvanced: boolean, + fontSize: string; + customThemeUrl: string; + customThemeMessage: CustomThemeMessage; + useCustomFontSize: boolean; + useSystemFont: boolean; + systemFont: string; + showAdvanced: boolean; } export default class AppearanceUserSettingsTab extends React.Component { @@ -349,7 +349,7 @@ export default class AppearanceUserSettingsTab extends React.Component { this.setState({ systemFont: value.target.value, - }) + }); SettingsStore.setValue("systemFont", null, SettingLevel.DEVICE, value.target.value); }} @@ -363,7 +363,7 @@ export default class AppearanceUserSettingsTab extends React.Component {toggle} {advanced} -
+
; } render() { diff --git a/src/dispatcher/payloads/UpdateFontSizePayload.ts b/src/dispatcher/payloads/UpdateFontSizePayload.ts index 3eac3e4607..6577acd594 100644 --- a/src/dispatcher/payloads/UpdateFontSizePayload.ts +++ b/src/dispatcher/payloads/UpdateFontSizePayload.ts @@ -18,7 +18,7 @@ import { ActionPayload } from "../payloads"; import { Action } from "../actions"; export interface UpdateFontSizePayload extends ActionPayload { - action: Action.UpdateFontSize, + action: Action.UpdateFontSize; /** * The font size to set the root to diff --git a/src/dispatcher/payloads/UpdateSystemFontPayload.ts b/src/dispatcher/payloads/UpdateSystemFontPayload.ts index 73475e10d5..aa59db5aa9 100644 --- a/src/dispatcher/payloads/UpdateSystemFontPayload.ts +++ b/src/dispatcher/payloads/UpdateSystemFontPayload.ts @@ -18,7 +18,7 @@ import { ActionPayload } from "../payloads"; import { Action } from "../actions"; export interface UpdateSystemFontPayload extends ActionPayload { - action: Action.UpdateSystemFont, + action: Action.UpdateSystemFont; /** * Specify whether to use a system font or the stylesheet font diff --git a/src/settings/watchers/FontWatcher.ts b/src/settings/watchers/FontWatcher.ts index cc843edb4d..9af5156704 100644 --- a/src/settings/watchers/FontWatcher.ts +++ b/src/settings/watchers/FontWatcher.ts @@ -38,7 +38,7 @@ export class FontWatcher implements IWatcher { this.setSystemFont({ useSystemFont: SettingsStore.getValue("useSystemFont"), font: SettingsStore.getValue("systemFont"), - }) + }); this.dispatcherRef = dis.register(this.onAction); } @@ -50,7 +50,7 @@ export class FontWatcher implements IWatcher { if (payload.action === Action.UpdateFontSize) { this.setRootFontSize(payload.size); } else if (payload.action === Action.UpdateSystemFont) { - this.setSystemFont(payload) + this.setSystemFont(payload); } }; @@ -65,5 +65,5 @@ export class FontWatcher implements IWatcher { private setSystemFont = ({useSystemFont, font}) => { document.body.style.fontFamily = useSystemFont ? font : ""; - } + }; } From aab42a291bd60cb2f27b881928e4fa67a93ec469 Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Mon, 22 Jun 2020 12:28:15 +0100 Subject: [PATCH 012/294] implement appearance tab nits - fix border colours in dark theme - lighten dark theme preview background - add missing return statement --- .../tabs/user/_AppearanceUserSettingsTab.scss | 17 +++++++++++------ res/themes/dark/css/_dark.scss | 4 ++-- res/themes/light/css/_light.scss | 2 +- .../views/elements/EventTilePreview.tsx | 2 +- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss index 044b5e2240..e2454336b8 100644 --- a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss @@ -30,7 +30,7 @@ limitations under the License. flex-direction: row; align-items: center; padding: 15px; - background: $font-slider-bg-color; + background: rgba($appearance-tab-border-color, 0.2); border-radius: 10px; font-size: 10px; margin-top: 24px; @@ -38,7 +38,7 @@ limitations under the License. } .mx_AppearanceUserSettingsTab_fontSlider_preview { - border: 1px solid $input-darker-bg-color; + border: 1px solid $appearance-tab-border-color; border-radius: 10px; padding: 0 16px 9px 16px; pointer-events: none; @@ -56,12 +56,14 @@ limitations under the License. font-size: 15px; padding-right: 20px; padding-left: 5px; + font-weight: 500; } .mx_AppearanceUserSettingsTab_fontSlider_largeText { font-size: 18px; padding-left: 20px; padding-right: 5px; + font-weight: 500; } .mx_AppearanceUserSettingsTab { @@ -115,7 +117,8 @@ limitations under the License. } &.mx_ThemeSelector_dark { - background-color: #181b21; + // 5% lightened version of 181b21 + background-color: #25282e; color: #f3f8fd; > input > div { @@ -163,10 +166,11 @@ limitations under the License. width: 300px; - border: 1px solid $input-darker-bg-color; + border: 1px solid $appearance-tab-border-color; border-radius: 10px; - .mx_EventTile_msgOption { + .mx_EventTile_msgOption, + .mx_MessageActionBar { display: none; } @@ -175,6 +179,7 @@ limitations under the License. display: flex; align-items: center; padding: 10px; + pointer-events: none; } .mx_RadioButton { @@ -188,7 +193,7 @@ limitations under the License. } .mx_RadioButton { - border-top: 1px solid $input-darker-bg-color; + border-top: 1px solid $appearance-tab-border-color; > input + div { border-color: rgba($muted-fg-color, 0.2); diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 50f3c08782..69fc91f222 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -198,8 +198,8 @@ $breadcrumb-placeholder-bg-color: #272c35; $user-tile-hover-bg-color: $header-panel-bg-color; -// FontSlider colors -$font-slider-bg-color: $room-highlight-color; +// Appearance tab colors +$appearance-tab-border-color: $room-highlight-color; // ***** Mixins! ***** diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 355cc1301c..57dc1fa5e0 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -327,7 +327,7 @@ $breadcrumb-placeholder-bg-color: #e8eef5; $user-tile-hover-bg-color: $header-panel-bg-color; // FontSlider colors -$font-slider-bg-color: rgba($input-darker-bg-color, 0.2); +$appearance-tab-border-color: $input-darker-bg-color; // ***** Mixins! ***** diff --git a/src/components/views/elements/EventTilePreview.tsx b/src/components/views/elements/EventTilePreview.tsx index fa600196e5..7d8b774955 100644 --- a/src/components/views/elements/EventTilePreview.tsx +++ b/src/components/views/elements/EventTilePreview.tsx @@ -108,7 +108,7 @@ export default class EventTilePreview extends React.Component { }, }; - + return event; } public render() { From ea929e575af9046d71a6089fba6e7646067607d0 Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Mon, 22 Jun 2020 16:02:23 +0100 Subject: [PATCH 013/294] Fix MessageActionBar --- res/css/views/rooms/_IRCLayout.scss | 5 ----- 1 file changed, 5 deletions(-) diff --git a/res/css/views/rooms/_IRCLayout.scss b/res/css/views/rooms/_IRCLayout.scss index 814a614007..94753f9473 100644 --- a/res/css/views/rooms/_IRCLayout.scss +++ b/res/css/views/rooms/_IRCLayout.scss @@ -121,11 +121,6 @@ $irc-line-height: $font-18px; } } - .mx_EventTile_line .mx_MessageActionBar, - .mx_EventTile_line .mx_ReplyThread_wrapper { - display: block; - } - .mx_EventTile_reply { order: 4; } From 7bc5ce7271b337d904ae880224973f08d4c42adf Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Mon, 22 Jun 2020 16:05:32 +0100 Subject: [PATCH 014/294] Underscore actions --- src/dispatcher/actions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index a03c731818..379a0a4451 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -73,10 +73,10 @@ export enum Action { /** * Sets the apps root font size. Should be used with UpdateFontSizePayload */ - UpdateFontSize = "update-font-size", + UpdateFontSize = "update_font_size", /** * Sets a system font. Should be used with UpdateSystemFontPayload */ - UpdateSystemFont = "update-system-font", + UpdateSystemFont = "update_system_font", } From 4db2ede67f9404ea3608d1827f6dfbd889fefdeb Mon Sep 17 00:00:00 2001 From: Slimane Selyan AMIRI Date: Mon, 22 Jun 2020 10:46:31 +0000 Subject: [PATCH 015/294] Translated using Weblate (Kabyle) Currently translated at 7.7% (177 of 2288 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/kab/ --- src/i18n/strings/kab.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/i18n/strings/kab.json b/src/i18n/strings/kab.json index 8d11469afc..d6cb7ce525 100644 --- a/src/i18n/strings/kab.json +++ b/src/i18n/strings/kab.json @@ -80,13 +80,13 @@ "Bicycle": "Azlalam", "Ball": "Balles", "Anchor": "Tamdeyt", - "Headphones": "Casque", + "Headphones": "", "Folder": "Akaram", "Upload": "Sali", "Remove": "Sfeḍ", "Show less": "Sken-d drus", "Show more": "Sken-d ugar", - "Warning!": "Ɣur-k!", + "Warning!": "Ɣur-k·m!", "Current password": "Awal uffir amiran", "Password": "Awal uffir", "New Password": "Awal uffir amaynut", @@ -100,11 +100,11 @@ "Keywords": "Awalen tisura", "Clear notifications": "Sfeḍ ilɣuyen", "Off": "Insa", - "Display Name": "Mefffer isem", + "Display Name": "", "Save": "Sekles", - "Disconnect": "Yeffeɣ", + "Disconnect": "", "Go back": "Uɣal ɣer deffir", - "Change": "Changer", + "Change": "Beddel", "Theme": "Asentel", "Success": "Yedda", "Profile": "Amaɣnu", @@ -117,11 +117,11 @@ "Keyboard Shortcuts": "Inegzumen n unasiw", "Versions": "Ileqman", "None": "Ula yiwen", - "Unsubscribe": "Se désabonner", + "Unsubscribe": "", "Ignore": "Ttu", "Subscribe": "Jerred", - "Preferences": "Tiwelhiwin", - "Composer": "Editeur", + "Preferences": "", + "Composer": "", "Timeline": "Amazray", "Microphone": "Asawaḍ", "Camera": "Takamiṛatt", From 099661c2aacd2eb360d8dcb6ac9e24972db16bc6 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 22 Jun 2020 10:18:38 -0600 Subject: [PATCH 016/294] Only fire setting changes for changed settings Previously we were firing updates for everything, which is bad. This has an effect of causing the room list to update itself every time the user goes to toggle some account settings. --- .../handlers/AccountSettingsHandler.js | 9 ++-- .../MatrixClientBackedSettingsHandler.js | 4 ++ .../handlers/RoomAccountSettingsHandler.js | 9 ++-- src/settings/handlers/RoomSettingsHandler.js | 13 +++-- src/utils/arrays.ts | 22 +++++++++ src/utils/objects.ts | 49 +++++++++++++++++++ 6 files changed, 97 insertions(+), 9 deletions(-) create mode 100644 src/utils/objects.ts diff --git a/src/settings/handlers/AccountSettingsHandler.js b/src/settings/handlers/AccountSettingsHandler.js index fea2e92c62..7b78c39c3a 100644 --- a/src/settings/handlers/AccountSettingsHandler.js +++ b/src/settings/handlers/AccountSettingsHandler.js @@ -18,6 +18,7 @@ limitations under the License. import {MatrixClientPeg} from '../../MatrixClientPeg'; import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler"; import {SettingLevel} from "../SettingsStore"; +import {objectKeyChanges} from "../../utils/objects"; const BREADCRUMBS_LEGACY_EVENT_TYPE = "im.vector.riot.breadcrumb_rooms"; const BREADCRUMBS_EVENT_TYPE = "im.vector.setting.breadcrumbs"; @@ -45,7 +46,7 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa newClient.on("accountData", this._onAccountData); } - _onAccountData(event) { + _onAccountData(event, prevEvent) { if (event.getType() === "org.matrix.preview_urls") { let val = event.getContent()['disable']; if (typeof(val) !== "boolean") { @@ -56,8 +57,10 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa this._watchers.notifyUpdate("urlPreviewsEnabled", null, SettingLevel.ACCOUNT, val); } else if (event.getType() === "im.vector.web.settings") { - // We can't really discern what changed, so trigger updates for everything - for (const settingName of Object.keys(event.getContent())) { + // Figure out what changed and fire those updates + const prevContent = prevEvent ? prevEvent.getContent() : {}; + const changedSettings = objectKeyChanges(prevContent, event.getContent()); + for (const settingName of changedSettings) { const val = event.getContent()[settingName]; this._watchers.notifyUpdate(settingName, null, SettingLevel.ACCOUNT, val); } diff --git a/src/settings/handlers/MatrixClientBackedSettingsHandler.js b/src/settings/handlers/MatrixClientBackedSettingsHandler.js index effe7ae9a7..63725b4dff 100644 --- a/src/settings/handlers/MatrixClientBackedSettingsHandler.js +++ b/src/settings/handlers/MatrixClientBackedSettingsHandler.js @@ -42,6 +42,10 @@ export default class MatrixClientBackedSettingsHandler extends SettingsHandler { MatrixClientBackedSettingsHandler._instances.push(this); } + get client() { + return MatrixClientBackedSettingsHandler._matrixClient; + } + initMatrixClient() { console.warn("initMatrixClient not overridden"); } diff --git a/src/settings/handlers/RoomAccountSettingsHandler.js b/src/settings/handlers/RoomAccountSettingsHandler.js index 1e9d3f7bed..1c818cef71 100644 --- a/src/settings/handlers/RoomAccountSettingsHandler.js +++ b/src/settings/handlers/RoomAccountSettingsHandler.js @@ -18,6 +18,7 @@ limitations under the License. import {MatrixClientPeg} from '../../MatrixClientPeg'; import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler"; import {SettingLevel} from "../SettingsStore"; +import {objectKeyChanges} from "../../utils/objects"; const ALLOWED_WIDGETS_EVENT_TYPE = "im.vector.setting.allowed_widgets"; @@ -40,7 +41,7 @@ export default class RoomAccountSettingsHandler extends MatrixClientBackedSettin newClient.on("Room.accountData", this._onAccountData); } - _onAccountData(event, room) { + _onAccountData(event, room, prevEvent) { const roomId = room.roomId; if (event.getType() === "org.matrix.room.preview_urls") { @@ -55,8 +56,10 @@ export default class RoomAccountSettingsHandler extends MatrixClientBackedSettin } else if (event.getType() === "org.matrix.room.color_scheme") { this._watchers.notifyUpdate("roomColor", roomId, SettingLevel.ROOM_ACCOUNT, event.getContent()); } else if (event.getType() === "im.vector.web.settings") { - // We can't really discern what changed, so trigger updates for everything - for (const settingName of Object.keys(event.getContent())) { + // Figure out what changed and fire those updates + const prevContent = prevEvent ? prevEvent.getContent() : {}; + const changedSettings = objectKeyChanges(prevContent, event.getContent()); + for (const settingName of changedSettings) { const val = event.getContent()[settingName]; this._watchers.notifyUpdate(settingName, roomId, SettingLevel.ROOM_ACCOUNT, val); } diff --git a/src/settings/handlers/RoomSettingsHandler.js b/src/settings/handlers/RoomSettingsHandler.js index 6407818450..4116f26220 100644 --- a/src/settings/handlers/RoomSettingsHandler.js +++ b/src/settings/handlers/RoomSettingsHandler.js @@ -18,6 +18,7 @@ limitations under the License. import {MatrixClientPeg} from '../../MatrixClientPeg'; import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler"; import {SettingLevel} from "../SettingsStore"; +import {objectKeyChanges} from "../../utils/objects"; /** * Gets and sets settings at the "room" level. @@ -38,8 +39,12 @@ export default class RoomSettingsHandler extends MatrixClientBackedSettingsHandl newClient.on("RoomState.events", this._onEvent); } - _onEvent(event) { + _onEvent(event, state, prevEvent) { const roomId = event.getRoomId(); + const room = this.client.getRoom(roomId); + if (!room) throw new Error(`Unknown room caused state update: ${roomId}`); + + if (state !== room.currentState) return; // ignore state updates which are not current if (event.getType() === "org.matrix.room.preview_urls") { let val = event.getContent()['disable']; @@ -51,8 +56,10 @@ export default class RoomSettingsHandler extends MatrixClientBackedSettingsHandl this._watchers.notifyUpdate("urlPreviewsEnabled", roomId, SettingLevel.ROOM, val); } else if (event.getType() === "im.vector.web.settings") { - // We can't really discern what changed, so trigger updates for everything - for (const settingName of Object.keys(event.getContent())) { + // Figure out what changed and fire those updates + const prevContent = prevEvent ? prevEvent.getContent() : {}; + const changedSettings = objectKeyChanges(prevContent, event.getContent()); + for (const settingName of changedSettings) { this._watchers.notifyUpdate(settingName, roomId, SettingLevel.ROOM, event.getContent()[settingName]); } } diff --git a/src/utils/arrays.ts b/src/utils/arrays.ts index fea376afcd..8175d89464 100644 --- a/src/utils/arrays.ts +++ b/src/utils/arrays.ts @@ -46,6 +46,28 @@ export function arrayDiff(a: T[], b: T[]): { added: T[], removed: T[] } { }; } +/** + * Returns the union of two arrays. + * @param a The first array. Must be defined. + * @param b The second array. Must be defined. + * @returns The union of the arrays. + */ +export function arrayUnion(a: T[], b: T[]): T[] { + return a.filter(i => b.includes(i)); +} + +/** + * Merges arrays, deduping contents using a Set. + * @param a The arrays to merge. + * @returns The merged array. + */ +export function arrayMerge(...a: T[][]): T[] { + return Array.from(a.reduce((c, v) => { + v.forEach(i => c.add(i)); + return c; + }, new Set())); +} + /** * Helper functions to perform LINQ-like queries on arrays. */ diff --git a/src/utils/objects.ts b/src/utils/objects.ts new file mode 100644 index 0000000000..cbb311cc48 --- /dev/null +++ b/src/utils/objects.ts @@ -0,0 +1,49 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { arrayDiff, arrayMerge, arrayUnion } from "./arrays"; + +/** + * Determines the keys added, changed, and removed between two objects. + * For changes, simple triple equal comparisons are done, not in-depth + * tree checking. + * @param a The first object. Must be defined. + * @param b The second object. Must be defined. + * @returns The difference between the keys of each object. + */ +export function objectDiff(a: any, b: any): { changed: string[], added: string[], removed: string[] } { + const aKeys = Object.keys(a); + const bKeys = Object.keys(b); + const keyDiff = arrayDiff(aKeys, bKeys); + const possibleChanges = arrayUnion(aKeys, bKeys); + const changes = possibleChanges.filter(k => a[k] !== b[k]); + + return {changed: changes, added: keyDiff.added, removed: keyDiff.removed}; +} + +/** + * Gets all the key changes (added, removed, or value difference) between + * two objects. Triple equals is used to compare values, not in-depth tree + * checking. + * @param a The first object. Must be defined. + * @param b The second object. Must be defined. + * @returns The keys which have been added, removed, or changed between the + * two objects. + */ +export function objectKeyChanges(a: any, b: any): string[] { + const diff = objectDiff(a, b); + return arrayMerge(diff.removed, diff.added, diff.changed); +} From 30d8dc06fcad02b0a2012d114b5a6fb5ecbf498c Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 19 Jun 2020 15:43:05 -0600 Subject: [PATCH 017/294] Increase bold weight for unread rooms For https://github.com/vector-im/riot-web/issues/14084 --- res/css/views/rooms/_RoomTile2.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/rooms/_RoomTile2.scss b/res/css/views/rooms/_RoomTile2.scss index 001499fea5..a97d1fd5b9 100644 --- a/res/css/views/rooms/_RoomTile2.scss +++ b/res/css/views/rooms/_RoomTile2.scss @@ -67,7 +67,7 @@ limitations under the License. } .mx_RoomTile2_name.mx_RoomTile2_nameHasUnreadEvents { - font-weight: 600; + font-weight: 700; } .mx_RoomTile2_messagePreview { From eeb408a0810e96afbb178652acbace7b0b6b0841 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 19 Jun 2020 15:44:37 -0600 Subject: [PATCH 018/294] Update badge logic for new setting and behaviour For https://github.com/vector-im/riot-web/issues/14084 --- .../views/rooms/NotificationBadge.tsx | 50 ++++++++++++++++--- src/components/views/rooms/RoomSublist2.tsx | 2 +- src/components/views/rooms/RoomTile2.tsx | 8 ++- src/settings/Settings.js | 5 ++ 4 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/components/views/rooms/NotificationBadge.tsx b/src/components/views/rooms/NotificationBadge.tsx index b742f8e8e7..65ccc88c5f 100644 --- a/src/components/views/rooms/NotificationBadge.tsx +++ b/src/components/views/rooms/NotificationBadge.tsx @@ -32,13 +32,14 @@ import ActiveRoomObserver from "../../../ActiveRoomObserver"; import { EventEmitter } from "events"; import { arrayDiff } from "../../../utils/arrays"; import { IDestroyable } from "../../../utils/IDestroyable"; +import SettingsStore from "../../../settings/SettingsStore"; export const NOTIFICATION_STATE_UPDATE = "update"; export enum NotificationColor { // Inverted (None -> Red) because we do integer comparisons on this None, // nothing special - Bold, // no badge, show as unread + Bold, // no badge, show as unread // TODO: This goes away with new notification structures Grey, // unread notified messages Red, // unread pings } @@ -53,18 +54,45 @@ interface IProps { notification: INotificationState; /** - * If true, the badge will conditionally display a badge without count for the user. + * If true, the badge will show a count if at all possible. This is typically + * used to override the user's preference for things like room sublists. */ - allowNoCount: boolean; + forceCount: boolean; + + /** + * The room ID, if any, the badge represents. + */ + roomId?: string; } interface IState { + showCounts: boolean; // whether or not to show counts. Independent of props.forceCount } export default class NotificationBadge extends React.PureComponent { + private countWatcherRef: string; + constructor(props: IProps) { super(props); this.props.notification.on(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate); + + this.state = { + showCounts: SettingsStore.getValue("Notifications.alwaysShowBadgeCounts", this.roomId), + }; + + this.countWatcherRef = SettingsStore.watchSetting( + "Notifications.alwaysShowBadgeCounts", this.roomId, + this.countPreferenceChanged, + ); + } + + private get roomId(): string { + // We should convert this to null for safety with the SettingsStore + return this.props.roomId || null; + } + + public componentWillUnmount() { + SettingsStore.unwatchSetting(this.countWatcherRef); } public componentDidUpdate(prevProps: Readonly) { @@ -75,24 +103,34 @@ export default class NotificationBadge extends React.PureComponent { + this.setState({showCounts: SettingsStore.getValue("Notifications.alwaysShowBadgeCounts", this.roomId)}); + }; + private onNotificationUpdate = () => { this.forceUpdate(); // notification state changed - update }; public render(): React.ReactElement { // Don't show a badge if we don't need to - if (this.props.notification.color <= NotificationColor.Bold) return null; + if (this.props.notification.color <= NotificationColor.None) return null; const hasNotif = this.props.notification.color >= NotificationColor.Red; const hasCount = this.props.notification.color >= NotificationColor.Grey; - const isEmptyBadge = this.props.allowNoCount && !localStorage.getItem("mx_rl_rt_badgeCount"); + const hasUnread = this.props.notification.color >= NotificationColor.Bold; + const couldBeEmpty = !this.state.showCounts || hasUnread; + let isEmptyBadge = couldBeEmpty && (!this.state.showCounts || !hasCount); + if (this.props.forceCount) { + isEmptyBadge = false; + if (!hasCount) return null; // Can't render a badge + } let symbol = this.props.notification.symbol || formatMinimalBadgeCount(this.props.notification.count); if (isEmptyBadge) symbol = ""; const classes = classNames({ 'mx_NotificationBadge': true, - 'mx_NotificationBadge_visible': hasCount, + 'mx_NotificationBadge_visible': isEmptyBadge ? true : hasCount, 'mx_NotificationBadge_highlighted': hasNotif, 'mx_NotificationBadge_dot': isEmptyBadge, 'mx_NotificationBadge_2char': symbol.length > 0 && symbol.length < 3, diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index 08a41570e3..562c307769 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -267,7 +267,7 @@ export default class RoomSublist2 extends React.Component { // TODO: Collapsed state - const badge = ; + const badge = ; let addRoomButton = null; if (!!this.props.onAddRoom) { diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index 9f4870d437..7f91b5ee9d 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -248,7 +248,13 @@ export default class RoomTile2 extends React.Component { 'mx_RoomTile2_minimized': this.props.isMinimized, }); - const badge = ; + const badge = ( + + ); // TODO: the original RoomTile uses state for the room name. Do we need to? let name = this.props.room.name; diff --git a/src/settings/Settings.js b/src/settings/Settings.js index ca8647e067..7cf0e629ec 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -188,6 +188,11 @@ export const SETTINGS = { default: true, invertedSettingName: 'MessageComposerInput.dontSuggestEmoji', }, + // TODO: Wire up appropriately to UI (FTUE notifications) + "Notifications.alwaysShowBadgeCounts": { + supportedLevels: LEVELS_ROOM_OR_ACCOUNT, + default: false, + }, "useCompactLayout": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, displayName: _td('Use compact timeline layout'), From 8201eed929025bc24ae341d74fef4a5e494883ed Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 19 Jun 2020 16:03:38 -0600 Subject: [PATCH 019/294] Encourage counts if the user has a mention (red state) --- src/components/views/rooms/NotificationBadge.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/NotificationBadge.tsx b/src/components/views/rooms/NotificationBadge.tsx index 65ccc88c5f..2ddf095b59 100644 --- a/src/components/views/rooms/NotificationBadge.tsx +++ b/src/components/views/rooms/NotificationBadge.tsx @@ -118,7 +118,7 @@ export default class NotificationBadge extends React.PureComponent= NotificationColor.Red; const hasCount = this.props.notification.color >= NotificationColor.Grey; const hasUnread = this.props.notification.color >= NotificationColor.Bold; - const couldBeEmpty = !this.state.showCounts || hasUnread; + const couldBeEmpty = (!this.state.showCounts || hasUnread) && !hasNotif; let isEmptyBadge = couldBeEmpty && (!this.state.showCounts || !hasCount); if (this.props.forceCount) { isEmptyBadge = false; From 241e0c12f0c9203b2ebb706d2ac8bc57b587f54d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 19 Jun 2020 15:45:48 -0600 Subject: [PATCH 020/294] Trigger room-specific watchers whenever a higher level change happens Otherwise the room list badges end up having to listen to `null` for a room ID, meaning they have to filter. The notification badge count setting is the first ever setting to watch based on a room ID, so there are no compatibility concerns with this change. --- src/settings/WatchManager.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/settings/WatchManager.js b/src/settings/WatchManager.js index 472b13966f..3f54ca929e 100644 --- a/src/settings/WatchManager.js +++ b/src/settings/WatchManager.js @@ -51,8 +51,17 @@ export class WatchManager { const roomWatchers = this._watchers[settingName]; const callbacks = []; - if (inRoomId !== null && roomWatchers[inRoomId]) callbacks.push(...roomWatchers[inRoomId]); - if (roomWatchers[null]) callbacks.push(...roomWatchers[null]); + if (inRoomId !== null && roomWatchers[inRoomId]) { + callbacks.push(...roomWatchers[inRoomId]); + } + + if (!inRoomId) { + // Fire updates to all the individual room watchers too, as they probably + // care about the change higher up. + callbacks.push(...Object.values(roomWatchers).reduce((r, a) => [...r, ...a], [])); + } else if (roomWatchers[null]) { + callbacks.push(...roomWatchers[null]); + } for (const callback of callbacks) { callback(inRoomId, atLevel, newValueAtLevel); From 63ad14ae1e7a06a456b6b43a0e11bf803d3c3049 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 22 Jun 2020 10:35:55 -0600 Subject: [PATCH 021/294] Clean up imports --- src/components/views/rooms/NotificationBadge.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/components/views/rooms/NotificationBadge.tsx b/src/components/views/rooms/NotificationBadge.tsx index 2ddf095b59..36c269beba 100644 --- a/src/components/views/rooms/NotificationBadge.tsx +++ b/src/components/views/rooms/NotificationBadge.tsx @@ -18,17 +18,11 @@ import React from "react"; import classNames from "classnames"; import { formatMinimalBadgeCount } from "../../../utils/FormattingUtils"; import { Room } from "matrix-js-sdk/src/models/room"; -import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex"; -import AccessibleButton from "../../views/elements/AccessibleButton"; -import RoomAvatar from "../../views/avatars/RoomAvatar"; -import dis from '../../../dispatcher/dispatcher'; -import { Key } from "../../../Keyboard"; import * as RoomNotifs from '../../../RoomNotifs'; import { EffectiveMembership, getEffectiveMembership } from "../../../stores/room-list/membership"; import * as Unread from '../../../Unread'; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; -import ActiveRoomObserver from "../../../ActiveRoomObserver"; import { EventEmitter } from "events"; import { arrayDiff } from "../../../utils/arrays"; import { IDestroyable } from "../../../utils/IDestroyable"; From fe65b7631dfd9b985e1e1f852a4e795dca617fd9 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 22 Jun 2020 10:57:08 -0600 Subject: [PATCH 022/294] Soften warning about lack of rooms in setting updates --- src/settings/handlers/RoomSettingsHandler.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/settings/handlers/RoomSettingsHandler.js b/src/settings/handlers/RoomSettingsHandler.js index 4116f26220..2ed82b577d 100644 --- a/src/settings/handlers/RoomSettingsHandler.js +++ b/src/settings/handlers/RoomSettingsHandler.js @@ -42,9 +42,12 @@ export default class RoomSettingsHandler extends MatrixClientBackedSettingsHandl _onEvent(event, state, prevEvent) { const roomId = event.getRoomId(); const room = this.client.getRoom(roomId); - if (!room) throw new Error(`Unknown room caused state update: ${roomId}`); - if (state !== room.currentState) return; // ignore state updates which are not current + // Note: the tests often fire setting updates that don't have rooms in the store, so + // we fail softly here. We shouldn't assume that the state being fired is current + // state, but we also don't need to explode just because we didn't find a room. + if (!room) console.warn(`Unknown room caused setting update: ${roomId}`); + if (room && state !== room.currentState) return; // ignore state updates which are not current if (event.getType() === "org.matrix.room.preview_urls") { let val = event.getContent()['disable']; From 784e73831bb070237e63ba2e75d527ac5b071a01 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 22 Jun 2020 11:23:38 -0600 Subject: [PATCH 023/294] Move setting to account only (no per-room) --- src/settings/Settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/Settings.js b/src/settings/Settings.js index 7cf0e629ec..028f355ab8 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -190,7 +190,7 @@ export const SETTINGS = { }, // TODO: Wire up appropriately to UI (FTUE notifications) "Notifications.alwaysShowBadgeCounts": { - supportedLevels: LEVELS_ROOM_OR_ACCOUNT, + supportedLevels: ['account'], default: false, }, "useCompactLayout": { From 9e3c101172c1f14c827de53d0d548ba79dbff6c6 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 22 Jun 2020 11:24:04 -0600 Subject: [PATCH 024/294] Clone reads of account data to prevent mutation --- src/settings/handlers/AccountSettingsHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/handlers/AccountSettingsHandler.js b/src/settings/handlers/AccountSettingsHandler.js index 7b78c39c3a..c396a9d4de 100644 --- a/src/settings/handlers/AccountSettingsHandler.js +++ b/src/settings/handlers/AccountSettingsHandler.js @@ -162,7 +162,7 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa const event = cli.getAccountData(eventType); if (!event || !event.getContent()) return null; - return event.getContent(); + return JSON.parse(JSON.stringify(event.getContent())); // clone to prevent mutation } _notifyBreadcrumbsUpdate(event) { From 37442b92aed4395e76fdce30580c6cf519fe7fd0 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 22 Jun 2020 19:02:03 +0100 Subject: [PATCH 025/294] Update read receipt remainder for internal font size change In https://github.com/matrix-org/matrix-react-sdk/pull/4725, we changed the internal font size from 15 to 10, but the `toRem` function (currently only used for read receipts remainders curiously) was not updated. This updates the function, which restores the remainders. Fixes https://github.com/vector-im/riot-web/issues/14127 --- src/utils/units.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/units.ts b/src/utils/units.ts index 54dd6b0523..03775f4c21 100644 --- a/src/utils/units.ts +++ b/src/utils/units.ts @@ -19,7 +19,7 @@ limitations under the License. // converts a pixel value to rem. export function toRem(pixelValue: number): string { - return pixelValue / 15 + "rem"; + return pixelValue / 10 + "rem"; } export function toPx(pixelValue: number): string { From 2319b25484e62249ebab71285092058bcf7a230a Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Mon, 22 Jun 2020 15:48:34 +0000 Subject: [PATCH 026/294] Translated using Weblate (Albanian) Currently translated at 99.9% (2286 of 2288 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/sq/ --- src/i18n/strings/sq.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sq.json b/src/i18n/strings/sq.json index 1f8f2ba48c..34507b1e3d 100644 --- a/src/i18n/strings/sq.json +++ b/src/i18n/strings/sq.json @@ -2517,5 +2517,12 @@ "Create a Recovery Key to store encryption keys & secrets with your account data. If you lose access to this login you’ll need it to unlock your data.": "Krijoni një Kyç Rimarrjesh që të depozitoni kyçe & të fshehta fshehtëzimi me të dhënat e llogarisë tuaj. Nëse humbni këto kredenciale, do t’ju duhet të shkyçni të dhënat tuaja.", "Create a Recovery Key": "Krijoni një Kyç Rimarrjesh", "Upgrade your Recovery Key": "Përmirësoni Kyçin tuaj të Rimarrjeve", - "Store your Recovery Key": "Depozitoni Kyçin tuaj të Rimarrjeve" + "Store your Recovery Key": "Depozitoni Kyçin tuaj të Rimarrjeve", + "Use the improved room list (will refresh to apply changes)": "Përdor listën e përmirësuar të dhomave (do të rifreskohet, që të aplikohen ndryshimet)", + "Enable IRC layout option in the appearance tab": "Aktivizoni te skeda e dukjes mundësinë për skemë IRC", + "Use custom size": "Përdor madhësi vetjake", + "Hey you. You're the best!": "Hej, ju. S’u ka kush shokun!", + "Message layout": "Skemë mesazhesh", + "Compact": "Kompakte", + "Modern": "Moderne" } From d875c3170d8caa21a4c0b15e5a3c5aeaff95b605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Mon, 22 Jun 2020 14:42:36 +0000 Subject: [PATCH 027/294] Translated using Weblate (Estonian) Currently translated at 67.3% (1539 of 2288 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/et/ --- src/i18n/strings/et.json | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index 70eec98ec5..b153536c9d 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -1592,5 +1592,31 @@ "We encountered an error trying to restore your previous session.": "Meil tekkis eelmise sessiooni taastamisel viga.", "If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "Kui sa varem oled kasutanud uuemat Riot'i versiooni, siis sinu pragune sessioon ei pruugi olla sellega ühilduv. Sulge see aken ja jätka selle uuema versiooni kasutamist.", "Clearing your browser's storage may fix the problem, but will sign you out and cause any encrypted chat history to become unreadable.": "Brauseri andmeruumi tühjendamine võib selle vea lahendada, kui samas logid sa ka välja ning kogu krüptitud vestlusajalugu muutub loetamatuks.", - "Verification Pending": "Verifikatsioon on ootel" + "Verification Pending": "Verifikatsioon on ootel", + "Back": "Tagasi", + "Send Custom Event": "Saada kohandatud sündmus", + "You must specify an event type!": "Sa pead määratlema sündmuse tüübi!", + "Event sent!": "Sündmus on saadetud!", + "Failed to send custom event.": "Kohandatud sündmuse saatmine ei õnnestunud.", + "Event Type": "Sündmuse tüüp", + "State Key": "Oleku võti", + "Event Content": "Sündmuse sisu", + "Send Account Data": "Saada kasutajakonto andmed", + "View Servers in Room": "Näita jututoas kasutatavaid servereid", + "Verification Requests": "Verifitseerimistaotlused", + "Toolbox": "Töövahendid", + "Developer Tools": "Arendusvahendid", + "An error has occurred.": "Tekkis viga.", + "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "Selle kasutaja usaldamiseks peaksid ta verifitseerima. Kui sa pruugid läbivalt krüptitud sõnumeid, siis kasutajate verifitseerimine tagab sulle täiendava meelerahu.", + "Verifying this user will mark their session as trusted, and also mark your session as trusted to them.": "Selle kasutaja verifitseerimisel märgitakse tema sessioon usaldusväärseks ning samuti märgitakse sinu sessioon tema jaoks usaldusväärseks.", + "Verify this device to mark it as trusted. Trusting this device gives you and other users extra peace of mind when using end-to-end encrypted messages.": "Selle seadme usaldamiseks peaksid ta verifitseerima. Kui sa pruugid läbivalt krüptitud sõnumeid, siis selle seadme usaldamine tagab sulle ja teistele kasutajatele täiendava meelerahu.", + "Verifying this device will mark it as trusted, and users who have verified with you will trust this device.": "Selle seadme verifitseerimisel märgitakse ta usaldusväärseks ning kõik kasutajad, kes sinuga on verifitseerimise läbi teinud, loevad ka selle seadme usaldusväärseks.", + "Waiting for partner to confirm...": "Ootan teise osapoole kinnitust...", + "Incoming Verification Request": "Saabuv verifitseerimispalve", + "Integrations are disabled": "Lõimingud ei ole kasutusel", + "Enable 'Manage Integrations' in Settings to do this.": "Selle tegevuse jaoks määra seadetes \"Halda lõiminguid\" kasutuselevõetuks.", + "Integrations not allowed": "Lõimingute kasutamine ei ole lubatud", + "Your Riot doesn't allow you to use an Integration Manager to do this. Please contact an admin.": "Sinu Riot ei võimalda selle tegevuse jaoks kasutada Lõimingute haldurit. Palun küsi lisateavet administraatorilt.", + "Failed to invite the following users to chat: %(csvUsers)s": "Järgnevate kasutajate vestlema kutsumine ei õnnestunud: %(csvUsers)s", + "We couldn't create your DM. Please check the users you want to invite and try again.": "Otsevestluse loomine ei õnnestunud. Palun kontrolli, et kasutajanimed oleks õiged ja proovi uuesti." } From 585af6ee8459d8408e19958dc0c7e900365192a8 Mon Sep 17 00:00:00 2001 From: Michael Albert Date: Mon, 22 Jun 2020 12:52:45 +0000 Subject: [PATCH 028/294] Translated using Weblate (German) Currently translated at 99.7% (2281 of 2288 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 143e3534c3..ee5eb489bc 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -2462,5 +2462,13 @@ "Enter your Recovery Key to continue.": "Gib deinen Wiederherstellungsschlüssel ein um fortzufahren.", "Create a Recovery Key": "Erzeuge einen Wiederherstellungsschlüssel", "Upgrade your Recovery Key": "Aktualisiere deinen Wiederherstellungsschlüssel", - "Store your Recovery Key": "Speichere deinen Wiederherstellungsschlüssel" + "Store your Recovery Key": "Speichere deinen Wiederherstellungsschlüssel", + "Light": "Hell", + "Dark": "Dunkel", + "Use the improved room list (will refresh to apply changes)": "Verwende die verbesserte Raumliste (lädt die Anwendung neu)", + "Use custom size": "Verwende individuelle Größe", + "Hey you. You're the best!": "Hey du. Du bist der Beste!", + "Message layout": "Nachrichtenlayout", + "Compact": "Kompakt", + "Modern": "Modern" } From 38bf1680a242ea8d50176791354a58326db54adc Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 22 Jun 2020 19:02:03 +0100 Subject: [PATCH 029/294] Update read receipt remainder for internal font size change In https://github.com/matrix-org/matrix-react-sdk/pull/4725, we changed the internal font size from 15 to 10, but the `toRem` function (currently only used for read receipts remainders curiously) was not updated. This updates the function, which restores the remainders. Fixes https://github.com/vector-im/riot-web/issues/14127 --- src/utils/units.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/units.ts b/src/utils/units.ts index 54dd6b0523..03775f4c21 100644 --- a/src/utils/units.ts +++ b/src/utils/units.ts @@ -19,7 +19,7 @@ limitations under the License. // converts a pixel value to rem. export function toRem(pixelValue: number): string { - return pixelValue / 15 + "rem"; + return pixelValue / 10 + "rem"; } export function toPx(pixelValue: number): string { From cf92fc37d42c10616835124543790aad1ff9b84d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 22 Jun 2020 12:51:53 -0600 Subject: [PATCH 030/294] Fix layout of minimized view for new room list --- res/css/views/rooms/_RoomSublist2.scss | 8 ++++---- src/components/views/rooms/RoomSublist2.tsx | 19 +++++++++++++++---- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss index 66615fb6a8..24151b15b0 100644 --- a/res/css/views/rooms/_RoomSublist2.scss +++ b/res/css/views/rooms/_RoomSublist2.scss @@ -304,18 +304,18 @@ limitations under the License. position: relative; .mx_RoomSublist2_badgeContainer { - order: 1; + order: 0; align-self: flex-end; margin-right: 0; } - .mx_RoomSublist2_headerText { - order: 2; + .mx_RoomSublist2_stickable { + order: 1; max-width: 100%; } .mx_RoomSublist2_auxButton { - order: 4; + order: 2; visibility: visible; width: 32px !important; // !important to override hover styles height: 32px !important; // !important to override hover styles diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index 08a41570e3..4a145ede11 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -291,7 +291,18 @@ export default class RoomSublist2 extends React.Component { 'mx_RoomSublist2_headerContainer_withAux': !!addRoomButton, }); + const badgeContainer = ( +
+ {badge} +
+ ); + // TODO: a11y (see old component) + // Note: the addRoomButton conditionally gets moved around + // the DOM depending on whether or not the list is minimized. + // If we're minimized, we want it below the header so it + // doesn't become sticky. + // The same applies to the notification badge. return (
@@ -307,11 +318,11 @@ export default class RoomSublist2 extends React.Component { {this.props.label} {this.renderMenu()} - {addRoomButton} -
- {badge} -
+ {this.props.isMinimized ? null : addRoomButton} + {this.props.isMinimized ? null : badgeContainer}
+ {this.props.isMinimized ? badgeContainer : null} + {this.props.isMinimized ? addRoomButton : null}
); }} From 1a427b8ff78522f48d9ca885d277da68b4010363 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 22 Jun 2020 13:09:42 -0600 Subject: [PATCH 031/294] Fix sticky headers over/under extending themselves in the new room list Fixes https://github.com/vector-im/riot-web/issues/14095 --- src/components/structures/LeftPanel2.tsx | 35 +++++++++++++++++----- src/components/structures/LoggedInView.tsx | 5 +++- src/utils/ResizeNotifier.js | 21 ++++++++++--- 3 files changed, 49 insertions(+), 12 deletions(-) diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index b5da44caef..378a24a70e 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -19,7 +19,6 @@ import TagPanel from "./TagPanel"; import classNames from "classnames"; import dis from "../../dispatcher/dispatcher"; import { _t } from "../../languageHandler"; -import SearchBox from "./SearchBox"; import RoomList2 from "../views/rooms/RoomList2"; import { Action } from "../../dispatcher/actions"; import { MatrixClientPeg } from "../../MatrixClientPeg"; @@ -30,6 +29,8 @@ import AccessibleButton from "../views/elements/AccessibleButton"; import RoomBreadcrumbs2 from "../views/rooms/RoomBreadcrumbs2"; import { BreadcrumbsStore } from "../../stores/BreadcrumbsStore"; import { UPDATE_EVENT } from "../../stores/AsyncStore"; +import ResizeNotifier from "../../utils/ResizeNotifier"; +import { createRef } from "react"; /******************************************************************* * CAUTION * @@ -41,6 +42,7 @@ import { UPDATE_EVENT } from "../../stores/AsyncStore"; interface IProps { isMinimized: boolean; + resizeNotifier: ResizeNotifier; } interface IState { @@ -49,6 +51,8 @@ interface IState { } export default class LeftPanel2 extends React.Component { + private listContainerRef: React.RefObject = createRef(); + // TODO: Properly support TagPanel // TODO: Properly support searching/filtering // TODO: Properly support breadcrumbs @@ -65,10 +69,15 @@ export default class LeftPanel2 extends React.Component { }; BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate); + + // We watch the middle panel because we don't actually get resized, the middle panel does. + // We listen to the noisy channel to avoid choppy reaction times. + this.props.resizeNotifier.on("middlePanelResizedNoisy", this.onResize); } public componentWillUnmount() { BreadcrumbsStore.instance.off(UPDATE_EVENT, this.onBreadcrumbsUpdate); + this.props.resizeNotifier.off("middlePanelResizedNoisy", this.onResize); } private onSearch = (term: string): void => { @@ -86,9 +95,7 @@ export default class LeftPanel2 extends React.Component { } }; - // TODO: Apply this on resize, init, etc for reliability - private onScroll = (ev: React.MouseEvent) => { - const list = ev.target as HTMLDivElement; + private handleStickyHeaders(list: HTMLDivElement) { const rlRect = list.getBoundingClientRect(); const bottom = rlRect.bottom; const top = rlRect.top; @@ -123,6 +130,18 @@ export default class LeftPanel2 extends React.Component { header.style.top = `unset`; } } + } + + // TODO: Apply this on resize, init, etc for reliability + private onScroll = (ev: React.MouseEvent) => { + const list = ev.target as HTMLDivElement; + this.handleStickyHeaders(list); + }; + + private onResize = () => { + console.log("Resize width"); + if (!this.listContainerRef.current) return; // ignore: no headers to sticky + this.handleStickyHeaders(this.listContainerRef.current); }; private renderHeader(): React.ReactNode { @@ -230,9 +249,11 @@ export default class LeftPanel2 extends React.Component {
); diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index f37f77b31b..1bc656e6a3 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -677,7 +677,10 @@ class LoggedInView extends React.PureComponent { if (SettingsStore.isFeatureEnabled("feature_new_room_list")) { // TODO: Supply props like collapsed and disabled to LeftPanel2 leftPanel = ( - + ); } diff --git a/src/utils/ResizeNotifier.js b/src/utils/ResizeNotifier.js index d65bc4bd07..f726a43e08 100644 --- a/src/utils/ResizeNotifier.js +++ b/src/utils/ResizeNotifier.js @@ -15,9 +15,13 @@ limitations under the License. */ /** - * Fires when the middle panel has been resized. + * Fires when the middle panel has been resized (throttled). * @event module:utils~ResizeNotifier#"middlePanelResized" */ +/** + * Fires when the middle panel has been resized by a pixel. + * @event module:utils~ResizeNotifier#"middlePanelResizedNoisy" + */ import { EventEmitter } from "events"; import { throttle } from "lodash"; @@ -29,15 +33,24 @@ export default class ResizeNotifier extends EventEmitter { this._throttledMiddlePanel = throttle(() => this.emit("middlePanelResized"), 200); } + _noisyMiddlePanel() { + this.emit("middlePanelResizedNoisy"); + } + + _updateMiddlePanel() { + this._throttledMiddlePanel(); + this._noisyMiddlePanel(); + } + // can be called in quick succession notifyLeftHandleResized() { // don't emit event for own region - this._throttledMiddlePanel(); + this._updateMiddlePanel(); } // can be called in quick succession notifyRightHandleResized() { - this._throttledMiddlePanel(); + this._updateMiddlePanel(); } // can be called in quick succession @@ -48,7 +61,7 @@ export default class ResizeNotifier extends EventEmitter { // taller than the available space this.emit("leftPanelResized"); - this._throttledMiddlePanel(); + this._updateMiddlePanel(); } } From 6c48966bf5417cacc4c50cf2f7bee779371d576c Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 22 Jun 2020 13:34:49 -0600 Subject: [PATCH 032/294] Have the theme switcher set the device-level theme to match settings Fixes https://github.com/vector-im/riot-web/issues/14111 This is a shortcut into the Appearance tab, so use the same level. It was an explicit decision to have the tab set the theme at the device level. --- src/components/structures/UserMenuButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/UserMenuButton.tsx b/src/components/structures/UserMenuButton.tsx index 6607fffdd1..04b1b03368 100644 --- a/src/components/structures/UserMenuButton.tsx +++ b/src/components/structures/UserMenuButton.tsx @@ -117,7 +117,7 @@ export default class UserMenuButton extends React.Component { SettingsStore.setValue("use_system_theme", null, SettingLevel.DEVICE, false); const newTheme = this.state.isDarkTheme ? "light" : "dark"; - SettingsStore.setValue("theme", null, SettingLevel.ACCOUNT, newTheme); + SettingsStore.setValue("theme", null, SettingLevel.DEVICE, newTheme); // set at same level as Appearance tab }; private onSettingsOpen = (ev: ButtonEvent, tabId: string) => { From 326f336425717ed0cb5e616752a2760abfdfd507 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Mon, 22 Jun 2020 18:28:38 +0000 Subject: [PATCH 033/294] Translated using Weblate (Estonian) Currently translated at 68.9% (1576 of 2288 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/et/ --- src/i18n/strings/et.json | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index b153536c9d..afe752015a 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -1618,5 +1618,42 @@ "Integrations not allowed": "Lõimingute kasutamine ei ole lubatud", "Your Riot doesn't allow you to use an Integration Manager to do this. Please contact an admin.": "Sinu Riot ei võimalda selle tegevuse jaoks kasutada Lõimingute haldurit. Palun küsi lisateavet administraatorilt.", "Failed to invite the following users to chat: %(csvUsers)s": "Järgnevate kasutajate vestlema kutsumine ei õnnestunud: %(csvUsers)s", - "We couldn't create your DM. Please check the users you want to invite and try again.": "Otsevestluse loomine ei õnnestunud. Palun kontrolli, et kasutajanimed oleks õiged ja proovi uuesti." + "We couldn't create your DM. Please check the users you want to invite and try again.": "Otsevestluse loomine ei õnnestunud. Palun kontrolli, et kasutajanimed oleks õiged ja proovi uuesti.", + "a new master key signature": "uus üldvõtme allkiri", + "a new cross-signing key signature": "uus risttunnustamise võtme allkiri", + "a device cross-signing signature": "seadme risttunnustamise allkiri", + "a key signature": "võtme allkiri", + "Riot encountered an error during upload of:": "Riot'is tekkis viga järgneva üleslaadimisel:", + "Cancelled signature upload": "Allkirja üleslaadimine on tühistatud", + "Unable to upload": "Üleslaadimine ei õnnestu", + "Signature upload success": "Allkirja üleslaadimine õnnestus", + "Signature upload failed": "Allkirja üleslaadimine ei õnnestunud", + "Address (optional)": "Aadress (valikuline)", + "Reject invitation": "Lükka kutse tagasi", + "Are you sure you want to reject the invitation?": "Kas sa oled kindel, et soovid lükata kutse tagasi?", + "Failed to set Direct Message status of room": "Jututoa otsevestluse oleku seadmine ei õnnestunud", + "Failed to forget room %(errCode)s": "Jututoa unustamine ei õnnestunud %(errCode)s", + "This homeserver would like to make sure you are not a robot.": "See server soovib kindlaks teha, et sa ei ole robot.", + "Country Dropdown": "Riikide valik", + "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use this app with an existing Matrix account on a different homeserver.": "Sa võid kasutada serveri kohandatud valikuid selleks, et määrates teise aadressi logida sisse teise Matrix'i serverisse. See võimaldab sul kasutada seda rakendust teises koduserveris hallatava olemasoleva Matrix'i kontoga.", + "Confirm your identity by entering your account password below.": "Tuvasta oma isik sisestades salasõna alljärgnevalt.", + "Please review and accept all of the homeserver's policies": "Palun vaata üle kõik koduserveri kasutustingimused ja nõustu nendega", + "Please review and accept the policies of this homeserver:": "Palun vaata üle selle koduserveri kasutustingimused ja nõustu nendega:", + "An email has been sent to %(emailAddress)s": "Saatsime e-kirja %(emailAddress)s aadressile", + "Please check your email to continue registration.": "Registreerimise jätkamiseks, palun vaata oma e-kirjad.", + "A text message has been sent to %(msisdn)s": "Saatsime tekstisõnumi telefoninumbrile %(msisdn)s", + "Please enter the code it contains:": "Palun sisesta seal kuvatud kood:", + "Code": "Kood", + "Submit": "Saada", + "Start authentication": "Alusta autentimist", + "Unable to validate homeserver/identity server": "Ei õnnestu valideerida koduserverit/isikutuvastusserverit", + "Your Modular server": "Sinu Modular-server", + "Enter the location of your Modular homeserver. It may use your own domain name or be a subdomain of modular.im.": "Sisesta oma Modular'i koduserveri aadress. See võib kasutada nii sinu oma domeeni, kui olla modular.im alamdomeen.", + "Sign in with SSO": "Logi sisse kasutades SSO'd ehk ühekordset autentimist", + "Failed to reject invitation": "Kutse tagasi lükkamine ei õnnestunud", + "This room is not public. You will not be able to rejoin without an invite.": "See ei ole avalik jututuba. Ilma kutseta sa ei saa uuesti liituda.", + "Failed to leave room": "Jututoast lahkumine ei õnnestunud", + "Can't leave Server Notices room": "Serveriteadete jututoast ei saa lahkuda", + "This room is used for important messages from the Homeserver, so you cannot leave it.": "Seda jututuba kasutatakse sinu koduserveri oluliste teadete jaoks ja seega sa ei saa sealt lahkuda.", + "Signed Out": "Välja logitud" } From 3054e6579a0776b0d045c0f9756f1d5c416ce746 Mon Sep 17 00:00:00 2001 From: ziriSut Date: Mon, 22 Jun 2020 19:00:29 +0000 Subject: [PATCH 034/294] Translated using Weblate (Kabyle) Currently translated at 8.4% (192 of 2288 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/kab/ --- src/i18n/strings/kab.json | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/kab.json b/src/i18n/strings/kab.json index d6cb7ce525..9245f89b54 100644 --- a/src/i18n/strings/kab.json +++ b/src/i18n/strings/kab.json @@ -216,7 +216,7 @@ "Send": "Azen", "Suggestions": "Isumar", "Go": "Ddu", - "Session name": "Nom de session", + "Session name": "Isem n tɣimit", "Send report": "Azen aneqqis", "Refresh": "Sismeḍ", "Email address": "Tansa email", @@ -346,5 +346,18 @@ "Cancel replying to a message": "Sefsex tiririt ɣef yizen", "Toggle microphone mute": "Rmed/sens tanusi n usawaḍ", "Toggle video on/off": "Rmed/sens tavidyut", - "Scroll up/down in the timeline": "Drurem gar afellay/addday n tesnakudt" + "Scroll up/down in the timeline": "Drurem gar afellay/addday n tesnakudt", + "Updating Riot": "Leqqem Riot", + "I don't want my encrypted messages": "Ur bɣiɣ ara izan-inu iwgelhanen", + "Manually export keys": "Sifeḍ s ufus tisura", + "Session ID": "Asulay n tqimit", + "Session key": "Tasarut n tɣimit", + "Please check your email and click on the link it contains. Once this is done, click continue.": "Ma ulac aɣilif, senqed imayl-ik/im syen sit ɣef useɣwen i yellan. Akken ara yemmed waya, sit ad tkemmleḍ.", + "This will allow you to reset your password and receive notifications.": "Ayagi ad ak(akem)-yeǧǧ ad twennzeḍ awal-ik/im uffir yerna ad d-tremseḍ ilɣa.", + "A username can only contain lower case letters, numbers and '=_-./'": "Isem n useqdac yezmer kan ad yegber isekkilen imeẓyanen, izwilen neɣ '=_-./'", + "Username invalid: %(errMessage)s": "Isem n useqdac d armeɣtu", + "An error occurred: %(error_string)s": "Tella-d tuccḍa: %(error_string)s", + "To get started, please pick a username!": "I wakken ad tebduḍ, ttxil-k/m fren isem n useqdac!", + "This will be your account name on the homeserver, or you can pick a different server.": "Wagi ad yili d isem-ik/im deg usebter agejdan, neɣ tzemreḍ ad tferneḍ aqeddac-nniḍen.", + "If you already have a Matrix account you can log in instead.": "Ma yella tesεiḍ yakan amiḍan di Matrix, tzemreḍ ad tkecmeḍ deg umḍq-nni." } From 1fe3e33dbf944cc0f078c18c253d019523af540d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 22 Jun 2020 14:14:43 -0600 Subject: [PATCH 035/294] Factor out cloning to a util and use it everywhere --- src/FromWidgetPostMessageApi.js | 5 +++-- src/ScalarMessaging.js | 5 +++-- src/components/views/terms/InlineTermsAgreement.js | 3 ++- src/settings/handlers/AccountSettingsHandler.js | 4 ++-- src/settings/handlers/RoomAccountSettingsHandler.js | 4 ++-- src/settings/handlers/RoomSettingsHandler.js | 4 ++-- src/utils/WidgetUtils.js | 3 ++- src/utils/objects.ts | 11 +++++++++++ src/widgets/WidgetApi.ts | 3 ++- 9 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/FromWidgetPostMessageApi.js b/src/FromWidgetPostMessageApi.js index 102afa6bf1..1b4aa19ebf 100644 --- a/src/FromWidgetPostMessageApi.js +++ b/src/FromWidgetPostMessageApi.js @@ -25,6 +25,7 @@ import RoomViewStore from "./stores/RoomViewStore"; import {IntegrationManagers} from "./integrations/IntegrationManagers"; import SettingsStore from "./settings/SettingsStore"; import {Capability} from "./widgets/WidgetApi"; +import {objectClone} from "./utils/objects"; const WIDGET_API_VERSION = '0.0.2'; // Current API version const SUPPORTED_WIDGET_API_VERSIONS = [ @@ -247,7 +248,7 @@ export default class FromWidgetPostMessageApi { * @param {Object} res Response data */ sendResponse(event, res) { - const data = JSON.parse(JSON.stringify(event.data)); + const data = objectClone(event.data); data.response = res; event.source.postMessage(data, event.origin); } @@ -260,7 +261,7 @@ export default class FromWidgetPostMessageApi { */ sendError(event, msg, nestedError) { console.error('Action:' + event.data.action + ' failed with message: ' + msg); - const data = JSON.parse(JSON.stringify(event.data)); + const data = objectClone(event.data); data.response = { error: { message: msg, diff --git a/src/ScalarMessaging.js b/src/ScalarMessaging.js index 315c2d86f4..b33aa57359 100644 --- a/src/ScalarMessaging.js +++ b/src/ScalarMessaging.js @@ -244,16 +244,17 @@ import RoomViewStore from './stores/RoomViewStore'; import { _t } from './languageHandler'; import {IntegrationManagers} from "./integrations/IntegrationManagers"; import {WidgetType} from "./widgets/WidgetType"; +import {objectClone} from "./utils/objects"; function sendResponse(event, res) { - const data = JSON.parse(JSON.stringify(event.data)); + const data = objectClone(event.data); data.response = res; event.source.postMessage(data, event.origin); } function sendError(event, msg, nestedError) { console.error("Action:" + event.data.action + " failed with message: " + msg); - const data = JSON.parse(JSON.stringify(event.data)); + const data = objectClone(event.data); data.response = { error: { message: msg, diff --git a/src/components/views/terms/InlineTermsAgreement.js b/src/components/views/terms/InlineTermsAgreement.js index bccd686cd3..55719fe57f 100644 --- a/src/components/views/terms/InlineTermsAgreement.js +++ b/src/components/views/terms/InlineTermsAgreement.js @@ -18,6 +18,7 @@ import React from "react"; import PropTypes from "prop-types"; import {_t, pickBestLanguage} from "../../../languageHandler"; import * as sdk from "../../.."; +import {objectClone} from "../../../utils/objects"; export default class InlineTermsAgreement extends React.Component { static propTypes = { @@ -56,7 +57,7 @@ export default class InlineTermsAgreement extends React.Component { } _togglePolicy = (index) => { - const policies = JSON.parse(JSON.stringify(this.state.policies)); // deep & cheap clone + const policies = objectClone(this.state.policies); policies[index].checked = !policies[index].checked; this.setState({policies}); }; diff --git a/src/settings/handlers/AccountSettingsHandler.js b/src/settings/handlers/AccountSettingsHandler.js index c396a9d4de..732ce6c550 100644 --- a/src/settings/handlers/AccountSettingsHandler.js +++ b/src/settings/handlers/AccountSettingsHandler.js @@ -18,7 +18,7 @@ limitations under the License. import {MatrixClientPeg} from '../../MatrixClientPeg'; import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler"; import {SettingLevel} from "../SettingsStore"; -import {objectKeyChanges} from "../../utils/objects"; +import {objectClone, objectKeyChanges} from "../../utils/objects"; const BREADCRUMBS_LEGACY_EVENT_TYPE = "im.vector.riot.breadcrumb_rooms"; const BREADCRUMBS_EVENT_TYPE = "im.vector.setting.breadcrumbs"; @@ -162,7 +162,7 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa const event = cli.getAccountData(eventType); if (!event || !event.getContent()) return null; - return JSON.parse(JSON.stringify(event.getContent())); // clone to prevent mutation + return objectClone(event.getContent()); // clone to prevent mutation } _notifyBreadcrumbsUpdate(event) { diff --git a/src/settings/handlers/RoomAccountSettingsHandler.js b/src/settings/handlers/RoomAccountSettingsHandler.js index 1c818cef71..b2af81779b 100644 --- a/src/settings/handlers/RoomAccountSettingsHandler.js +++ b/src/settings/handlers/RoomAccountSettingsHandler.js @@ -18,7 +18,7 @@ limitations under the License. import {MatrixClientPeg} from '../../MatrixClientPeg'; import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler"; import {SettingLevel} from "../SettingsStore"; -import {objectKeyChanges} from "../../utils/objects"; +import {objectClone, objectKeyChanges} from "../../utils/objects"; const ALLOWED_WIDGETS_EVENT_TYPE = "im.vector.setting.allowed_widgets"; @@ -137,6 +137,6 @@ export default class RoomAccountSettingsHandler extends MatrixClientBackedSettin const event = room.getAccountData(eventType); if (!event || !event.getContent()) return null; - return event.getContent(); + return objectClone(event.getContent()); // clone to prevent mutation } } diff --git a/src/settings/handlers/RoomSettingsHandler.js b/src/settings/handlers/RoomSettingsHandler.js index 2ed82b577d..d8e775742c 100644 --- a/src/settings/handlers/RoomSettingsHandler.js +++ b/src/settings/handlers/RoomSettingsHandler.js @@ -18,7 +18,7 @@ limitations under the License. import {MatrixClientPeg} from '../../MatrixClientPeg'; import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler"; import {SettingLevel} from "../SettingsStore"; -import {objectKeyChanges} from "../../utils/objects"; +import {objectClone, objectKeyChanges} from "../../utils/objects"; /** * Gets and sets settings at the "room" level. @@ -117,6 +117,6 @@ export default class RoomSettingsHandler extends MatrixClientBackedSettingsHandl const event = room.currentState.getStateEvents(eventType, ""); if (!event || !event.getContent()) return null; - return event.getContent(); + return objectClone(event.getContent()); // clone to prevent mutation } } diff --git a/src/utils/WidgetUtils.js b/src/utils/WidgetUtils.js index b48ec481ba..f7f4be202b 100644 --- a/src/utils/WidgetUtils.js +++ b/src/utils/WidgetUtils.js @@ -31,6 +31,7 @@ import {IntegrationManagers} from "../integrations/IntegrationManagers"; import {Capability} from "../widgets/WidgetApi"; import {Room} from "matrix-js-sdk/src/models/room"; import {WidgetType} from "../widgets/WidgetType"; +import {objectClone} from "./objects"; export default class WidgetUtils { /* Returns true if user is able to send state events to modify widgets in this room @@ -222,7 +223,7 @@ export default class WidgetUtils { const client = MatrixClientPeg.get(); // Get the current widgets and clone them before we modify them, otherwise // we'll modify the content of the old event. - const userWidgets = JSON.parse(JSON.stringify(WidgetUtils.getUserWidgets())); + const userWidgets = objectClone(WidgetUtils.getUserWidgets()); // Delete existing widget with ID try { diff --git a/src/utils/objects.ts b/src/utils/objects.ts index cbb311cc48..14fa928ce2 100644 --- a/src/utils/objects.ts +++ b/src/utils/objects.ts @@ -47,3 +47,14 @@ export function objectKeyChanges(a: any, b: any): string[] { const diff = objectDiff(a, b); return arrayMerge(diff.removed, diff.added, diff.changed); } + +/** + * Clones an object by running it through JSON parsing. Note that this + * will destroy any complicated object types which do not translate to + * JSON. + * @param obj The object to clone. + * @returns The cloned object + */ +export function objectClone(obj: any): any { + return JSON.parse(JSON.stringify(obj)); +} diff --git a/src/widgets/WidgetApi.ts b/src/widgets/WidgetApi.ts index 795c6648ef..d5f2f2258e 100644 --- a/src/widgets/WidgetApi.ts +++ b/src/widgets/WidgetApi.ts @@ -19,6 +19,7 @@ limitations under the License. import { randomString } from "matrix-js-sdk/src/randomstring"; import { EventEmitter } from "events"; +import { objectClone } from "../utils/objects"; export enum Capability { Screenshot = "m.capability.screenshot", @@ -140,7 +141,7 @@ export class WidgetApi extends EventEmitter { private replyToRequest(payload: ToWidgetRequest, reply: any) { if (!window.parent) return; - const request = JSON.parse(JSON.stringify(payload)); + const request = objectClone(payload); request.response = reply; window.parent.postMessage(request, this.origin); From fb551781c20dc7f872e866b615126d1fd1b70a55 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 22 Jun 2020 14:52:17 -0600 Subject: [PATCH 036/294] Force DMs to always be red notifications This also passes the tagId to the sublist so it doesn't have to rip it out of the `layout`. It doesn't get a layout until later anyways, which causes some null issues. --- .../views/rooms/NotificationBadge.tsx | 40 +++++++++++++++++-- src/components/views/rooms/RoomList2.tsx | 1 + src/components/views/rooms/RoomSublist2.tsx | 20 +++++----- src/components/views/rooms/RoomTile2.tsx | 8 +++- 4 files changed, 54 insertions(+), 15 deletions(-) diff --git a/src/components/views/rooms/NotificationBadge.tsx b/src/components/views/rooms/NotificationBadge.tsx index 36c269beba..37b61714b9 100644 --- a/src/components/views/rooms/NotificationBadge.tsx +++ b/src/components/views/rooms/NotificationBadge.tsx @@ -27,6 +27,7 @@ import { EventEmitter } from "events"; import { arrayDiff } from "../../../utils/arrays"; import { IDestroyable } from "../../../utils/IDestroyable"; import SettingsStore from "../../../settings/SettingsStore"; +import { DefaultTagID, TagID } from "../../../stores/room-list/models"; export const NOTIFICATION_STATE_UPDATE = "update"; @@ -139,7 +140,7 @@ export default class NotificationBadge extends React.PureComponent { components.push( { super(props); this.state = { - notificationState: new ListNotificationState(this.props.isInvite), + notificationState: new ListNotificationState(this.props.isInvite, this.props.tagId), menuDisplayed: false, }; this.state.notificationState.setRooms(this.props.rooms); @@ -130,13 +132,13 @@ export default class RoomSublist2 extends React.Component { }; private onUnreadFirstChanged = async () => { - const isUnreadFirst = RoomListStore.instance.getListOrder(this.props.layout.tagId) === ListAlgorithm.Importance; + const isUnreadFirst = RoomListStore.instance.getListOrder(this.props.tagId) === ListAlgorithm.Importance; const newAlgorithm = isUnreadFirst ? ListAlgorithm.Natural : ListAlgorithm.Importance; - await RoomListStore.instance.setListOrder(this.props.layout.tagId, newAlgorithm); + await RoomListStore.instance.setListOrder(this.props.tagId, newAlgorithm); }; private onTagSortChanged = async (sort: SortAlgorithm) => { - await RoomListStore.instance.setTagSorting(this.props.layout.tagId, sort); + await RoomListStore.instance.setTagSorting(this.props.tagId, sort); }; private onMessagePreviewChanged = () => { @@ -176,7 +178,7 @@ export default class RoomSublist2 extends React.Component { key={`room-${room.roomId}`} showMessagePreview={this.props.layout.showPreviews} isMinimized={this.props.isMinimized} - tag={this.props.layout.tagId} + tag={this.props.tagId} /> ); } @@ -189,8 +191,8 @@ export default class RoomSublist2 extends React.Component { let contextMenu = null; if (this.state.menuDisplayed) { const elementRect = this.menuButtonRef.current.getBoundingClientRect(); - const isAlphabetical = RoomListStore.instance.getTagSorting(this.props.layout.tagId) === SortAlgorithm.Alphabetic; - const isUnreadFirst = RoomListStore.instance.getListOrder(this.props.layout.tagId) === ListAlgorithm.Importance; + const isAlphabetical = RoomListStore.instance.getTagSorting(this.props.tagId) === SortAlgorithm.Alphabetic; + const isUnreadFirst = RoomListStore.instance.getListOrder(this.props.tagId) === ListAlgorithm.Importance; contextMenu = ( { this.onTagSortChanged(SortAlgorithm.Recent)} checked={!isAlphabetical} - name={`mx_${this.props.layout.tagId}_sortBy`} + name={`mx_${this.props.tagId}_sortBy`} > {_t("Activity")} this.onTagSortChanged(SortAlgorithm.Alphabetic)} checked={isAlphabetical} - name={`mx_${this.props.layout.tagId}_sortBy`} + name={`mx_${this.props.tagId}_sortBy`} > {_t("A-Z")} diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index 7f91b5ee9d..18b4ee8185 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -26,7 +26,11 @@ import RoomAvatar from "../../views/avatars/RoomAvatar"; import dis from '../../../dispatcher/dispatcher'; import { Key } from "../../../Keyboard"; import ActiveRoomObserver from "../../../ActiveRoomObserver"; -import NotificationBadge, { INotificationState, NotificationColor, RoomNotificationState } from "./NotificationBadge"; +import NotificationBadge, { + INotificationState, + NotificationColor, + TagSpecificNotificationState +} from "./NotificationBadge"; import { _t } from "../../../languageHandler"; import { ContextMenu, ContextMenuButton } from "../../structures/ContextMenu"; import { DefaultTagID, TagID } from "../../../stores/room-list/models"; @@ -79,7 +83,7 @@ export default class RoomTile2 extends React.Component { this.state = { hover: false, - notificationState: new RoomNotificationState(this.props.room), + notificationState: new TagSpecificNotificationState(this.props.room, this.props.tag), selected: ActiveRoomObserver.activeRoomId === this.props.room.roomId, generalMenuDisplayed: false, }; From fc5ee64fce2f14a7dfcf971effde6ddb0beb9e5a Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 22 Jun 2020 15:12:30 -0600 Subject: [PATCH 037/294] Fix read receipt handling in the new room list Fixes https://github.com/vector-im/riot-web/issues/14064 Fixes https://github.com/vector-im/riot-web/issues/14082 Turns out the event doesn't reference a room, so we need to use the accompanied room reference instead. --- src/components/views/rooms/NotificationBadge.tsx | 11 +++++++++-- src/stores/room-list/RoomListStore2.ts | 6 +++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/components/views/rooms/NotificationBadge.tsx b/src/components/views/rooms/NotificationBadge.tsx index 37b61714b9..523b5a55cc 100644 --- a/src/components/views/rooms/NotificationBadge.tsx +++ b/src/components/views/rooms/NotificationBadge.tsx @@ -28,6 +28,7 @@ import { arrayDiff } from "../../../utils/arrays"; import { IDestroyable } from "../../../utils/IDestroyable"; import SettingsStore from "../../../settings/SettingsStore"; import { DefaultTagID, TagID } from "../../../stores/room-list/models"; +import { readReceiptChangeIsFor } from "../../../utils/read-receipts"; export const NOTIFICATION_STATE_UPDATE = "update"; @@ -147,7 +148,7 @@ export class RoomNotificationState extends EventEmitter implements IDestroyable, constructor(private room: Room) { super(); - this.room.on("Room.receipt", this.handleRoomEventUpdate); + this.room.on("Room.receipt", this.handleReadReceipt); this.room.on("Room.timeline", this.handleRoomEventUpdate); this.room.on("Room.redaction", this.handleRoomEventUpdate); MatrixClientPeg.get().on("Event.decrypted", this.handleRoomEventUpdate); @@ -171,7 +172,7 @@ export class RoomNotificationState extends EventEmitter implements IDestroyable, } public destroy(): void { - this.room.removeListener("Room.receipt", this.handleRoomEventUpdate); + this.room.removeListener("Room.receipt", this.handleReadReceipt); this.room.removeListener("Room.timeline", this.handleRoomEventUpdate); this.room.removeListener("Room.redaction", this.handleRoomEventUpdate); if (MatrixClientPeg.get()) { @@ -179,6 +180,12 @@ export class RoomNotificationState extends EventEmitter implements IDestroyable, } } + private handleReadReceipt = (event: MatrixEvent, room: Room) => { + if (!readReceiptChangeIsFor(event, MatrixClientPeg.get())) return; // not our own - ignore + if (room.roomId !== this.room.roomId) return; // not for us - ignore + this.updateNotificationState(); + }; + private handleRoomEventUpdate = (event: MatrixEvent) => { const roomId = event.getRoomId(); diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index 9684e338f8..99eee82d4e 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -158,12 +158,12 @@ export class RoomListStore2 extends AsyncStore { // First see if the receipt event is for our own user. If it was, trigger // a room update (we probably read the room on a different device). if (readReceiptChangeIsFor(payload.event, this.matrixClient)) { - console.log(`[RoomListDebug] Got own read receipt in ${payload.event.roomId}`); - const room = this.matrixClient.getRoom(payload.event.roomId); + const room = payload.room; if (!room) { - console.warn(`Own read receipt was in unknown room ${payload.event.roomId}`); + console.warn(`Own read receipt was in unknown room ${room.roomId}`); return; } + console.log(`[RoomListDebug] Got own read receipt in ${room.roomId}`); await this.handleRoomUpdate(room, RoomUpdateCause.ReadReceipt); return; } From 115c850d35b64491bb3170374bc8d91f332c9e88 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 22 Jun 2020 15:57:13 -0600 Subject: [PATCH 038/294] Use the correct timeline reference for message previews Fixes https://github.com/vector-im/riot-web/issues/14083 (hopefully) This is the same logic used by `Unread.js`, so should be correct. --- src/stores/MessagePreviewStore.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/stores/MessagePreviewStore.ts b/src/stores/MessagePreviewStore.ts index 64d65a72f3..9e225d1709 100644 --- a/src/stores/MessagePreviewStore.ts +++ b/src/stores/MessagePreviewStore.ts @@ -78,9 +78,8 @@ export class MessagePreviewStore extends AsyncStoreWithClient { } private generatePreview(room: Room) { - const timeline = room.getLiveTimeline(); - if (!timeline) return; // usually only happens in tests - const events = timeline.getEvents(); + const events = room.timeline; + if (!events) return; // should only happen in tests for (let i = events.length - 1; i >= 0; i--) { if (i === events.length - MAX_EVENTS_BACKWARDS) return; // limit reached From 22269de5b07607f4b0d26c4a970475740e376b35 Mon Sep 17 00:00:00 2001 From: ziriSut Date: Mon, 22 Jun 2020 22:15:09 +0000 Subject: [PATCH 039/294] Translated using Weblate (Kabyle) Currently translated at 9.7% (223 of 2288 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/kab/ --- src/i18n/strings/kab.json | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/i18n/strings/kab.json b/src/i18n/strings/kab.json index 9245f89b54..285fb6cbf1 100644 --- a/src/i18n/strings/kab.json +++ b/src/i18n/strings/kab.json @@ -2,12 +2,12 @@ "Confirm": "Sentem", "Analytics": "Tiselḍin", "Error": "Tuccḍa", - "Dismiss": "Agwi", + "Dismiss": "Agi", "OK": "IH", "Permission Required": "Tasiregt tlaq", "Continue": "Kemmel", - "Cancel": "Semmet", - "Sun": "Ace", + "Cancel": "Sefsex", + "Sun": "Iṭij", "Mon": "Ari", "Tue": "Ara", "Wed": "Aha", @@ -359,5 +359,32 @@ "An error occurred: %(error_string)s": "Tella-d tuccḍa: %(error_string)s", "To get started, please pick a username!": "I wakken ad tebduḍ, ttxil-k/m fren isem n useqdac!", "This will be your account name on the homeserver, or you can pick a different server.": "Wagi ad yili d isem-ik/im deg usebter agejdan, neɣ tzemreḍ ad tferneḍ aqeddac-nniḍen.", - "If you already have a Matrix account you can log in instead.": "Ma yella tesεiḍ yakan amiḍan di Matrix, tzemreḍ ad tkecmeḍ deg umḍq-nni." + "If you already have a Matrix account you can log in instead.": "Ma yella tesεiḍ yakan amiḍan di Matrix, tzemreḍ ad tkecmeḍ deg umḍq-nni.", + "Call Failed": "Ur iddi ara usiwel", + "Call Timeout": "Akud n uṛaǧu n usiwel", + "Try using turn.matrix.org": "Ɛreḍ aseqdec n turn.matrix.org", + "Unable to capture screen": "Tuṭṭfa n ugdil ulamek", + "Existing Call": "Asiwel amiran", + "You are already in a call.": "Aql-ak(qkem)-id yakan tessawaleḍ.", + "VoIP is unsupported": "VoIP ur tettusefrak ara", + "You cannot place VoIP calls in this browser.": "Ur tezmireḍ ara ad tesεeddiḍ asiwel VoIP deg yiminig-a.", + "You cannot place a call with yourself.": "Ur tezmireḍ ara a temsawaleḍ d yiman-ik.", + "Call in Progress": "Asiwel iteddu", + "A call is currently being placed!": "Yella usiwel ila iteddu!", + "A call is already in progress!": "Yella usiwel ila iteddun akka tura!", + "Replying With Files": "Tiririt s yifuyla", + "The file '%(fileName)s' failed to upload.": "Yegguma ad d-yali '%(fileName)s' ufaylu.", + "Upload Failed": "Asali ur yeddi ara", + "Enter passphrase": "Sekcem tafyirt tuffirt", + "Setting up keys": "Asebded n tsura", + "%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s", + "%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(time)s", + "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s", + "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s", + "Who would you like to add to this community?": "Anwa i tebɣiḍ ad t-ternuḍ ɣer temɣiwent-a?", + "Name or Matrix ID": "Isem neɣ asulay n Matrix", + "Invite to Community": "Ɛreḍ-d ɣer temɣiwent", + "Room name or address": "Isem neɣ tansa n texxamt", + "Add to community": "Rnu ɣer temɣiwent", + "Unnamed Room": "Taxxamt war isem" } From 680e997a94ff7b34ccfa6638f6bd8b33fe8b8896 Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Tue, 23 Jun 2020 13:38:50 +0100 Subject: [PATCH 040/294] Cleanup tooltip classnames --- src/components/views/elements/Field.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/views/elements/Field.tsx b/src/components/views/elements/Field.tsx index 9d53576259..834edff7df 100644 --- a/src/components/views/elements/Field.tsx +++ b/src/components/views/elements/Field.tsx @@ -244,9 +244,8 @@ export default class Field extends React.PureComponent { const Tooltip = sdk.getComponent("elements.Tooltip"); let fieldTooltip; if (tooltipContent || this.state.feedback) { - const addClassName = tooltipClassName ? tooltipClassName : ''; fieldTooltip = ; From 54ba75afd7e8ebc6809c7dcb2c678ee43bbece7b Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 23 Jun 2020 14:48:23 +0100 Subject: [PATCH 041/294] Upgrade matrix-js-sdk to 7.0.0 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 06c4c43622..07cb11d45b 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.0.0-rc.1", + "matrix-js-sdk": "7.0.0", "minimist": "^1.2.0", "pako": "^1.0.5", "parse5": "^5.1.1", diff --git a/yarn.lock b/yarn.lock index 2bb99f4602..2aa39a257f 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.0.0-rc.1: - version "7.0.0-rc.1" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-7.0.0-rc.1.tgz#95a258218f5c5ec73ec4be510b28768c35809a0b" - integrity sha512-1znl0d2UxU6Mmimy+pMSQP1lQfsmDb9jxiKV5sfMvTBsLtUE2cTqEBVDNVoOHL4UJ9U4oMLsrBgu3sELkgSJLQ== +matrix-js-sdk@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-7.0.0.tgz#da2b24e57574379c3d8f7065eb68ea6c479d9806" + integrity sha512-0i1NmfwS5HzEPPjqUAXpw81o+8DImBS67QQBemJiM6D/imU3KFBacdhkmgjMLKXnAfTy6a+aCGfYBfVolfmNQw== dependencies: "@babel/runtime" "^7.8.3" another-json "^0.2.0" From 6ce8584337cb74695d4d8d711301b136468f323d Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 23 Jun 2020 15:04:39 +0100 Subject: [PATCH 042/294] Implement first screen (recovery key / passphrase choice) --- .../_CreateSecretStorageDialog.scss | 18 ++++ .../views/elements/_StyledRadioButton.scss | 13 ++- res/img/feather-customised/secure-backup.svg | 11 ++ res/img/feather-customised/secure-phrase.svg | 11 ++ .../CreateSecretStorageDialog.js | 102 ++++++++++++++++-- .../views/elements/StyledRadioButton.tsx | 2 +- src/i18n/strings/en_EN.json | 6 ++ 7 files changed, 151 insertions(+), 12 deletions(-) create mode 100644 res/img/feather-customised/secure-backup.svg create mode 100644 res/img/feather-customised/secure-phrase.svg diff --git a/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss b/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss index 63e5a3de09..28df5039f1 100644 --- a/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss +++ b/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss @@ -59,6 +59,24 @@ limitations under the License. display: block; } +.mx_CreateSecretStorageDialog_primaryContainer .mx_RadioButton { + margin-bottom: 16px; + padding: 11px; +} + +.mx_CreateSecretStorageDialog_optionTitle { + color: $dialog-title-fg-color; + font-weight: 600; + font-size: $font-18px; +} + +.mx_CreateSecretStorageDialog_optionIcon { + width: 24px; + margin-right: 8px; + position: relative; + top: 5px; +} + .mx_CreateSecretStorageDialog_passPhraseContainer { display: flex; align-items: flex-start; diff --git a/res/css/views/elements/_StyledRadioButton.scss b/res/css/views/elements/_StyledRadioButton.scss index c2edb359dc..eb73cec5b5 100644 --- a/res/css/views/elements/_StyledRadioButton.scss +++ b/res/css/views/elements/_StyledRadioButton.scss @@ -25,16 +25,21 @@ limitations under the License. position: relative; display: flex; - align-items: center; + align-items: baseline; flex-grow: 1; - > span { + border: 1px solid $input-darker-bg-color; + border-radius: 8px; + + > .mx_RadioButton_content { flex-grow: 1; display: flex; margin-left: 8px; margin-right: 8px; + + flex-direction: column; } .mx_RadioButton_spacer { @@ -105,3 +110,7 @@ limitations under the License. } } } + +.mx_RadioButton_checked { + border-color: $accent-color; +} diff --git a/res/img/feather-customised/secure-backup.svg b/res/img/feather-customised/secure-backup.svg new file mode 100644 index 0000000000..c06f93c1fe --- /dev/null +++ b/res/img/feather-customised/secure-backup.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/res/img/feather-customised/secure-phrase.svg b/res/img/feather-customised/secure-phrase.svg new file mode 100644 index 0000000000..eb13d3f048 --- /dev/null +++ b/res/img/feather-customised/secure-phrase.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index d7b79c2cfa..08a1dc5d9e 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -26,20 +26,26 @@ import { promptForBackupPassphrase } from '../../../../CrossSigningManager'; import {copyNode} from "../../../../utils/strings"; import {SSOAuthEntry} from "../../../../components/views/auth/InteractiveAuthEntryComponents"; import PassphraseField from "../../../../components/views/auth/PassphraseField"; +import StyledRadioButton from '../../../../components/views/elements/StyledRadioButton'; const PHASE_LOADING = 0; const PHASE_LOADERROR = 1; -const PHASE_MIGRATE = 2; -const PHASE_PASSPHRASE = 3; -const PHASE_PASSPHRASE_CONFIRM = 4; -const PHASE_SHOWKEY = 5; -const PHASE_KEEPITSAFE = 6; -const PHASE_STORING = 7; -const PHASE_DONE = 8; -const PHASE_CONFIRM_SKIP = 9; +const PHASE_CHOOSE_KEY_PASSPHRASE = 2; +const PHASE_MIGRATE = 3; +const PHASE_PASSPHRASE = 4; +const PHASE_PASSPHRASE_CONFIRM = 5; +const PHASE_SHOWKEY = 6; +const PHASE_KEEPITSAFE = 7; +const PHASE_STORING = 8; +const PHASE_DONE = 9; +const PHASE_CONFIRM_SKIP = 10; const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc. +// these end up as strings from being values in the radio buttons, so just use strings +const CREATESTORAGE_OPTION_KEY = 'key'; +const CREATESTORAGE_OPTION_PASSPHRASE = 'passphrase'; + /* * Walks the user through the process of creating a passphrase to guard Secure * Secret Storage in account data. @@ -79,6 +85,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent { accountPasswordCorrect: null, // status of the key backup toggle switch useKeyBackup: true, + + passPhraseKeySelected: CREATESTORAGE_OPTION_KEY, }; this._passphraseField = createRef(); @@ -110,7 +118,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { ); const { force } = this.props; - const phase = (backupInfo && !force) ? PHASE_MIGRATE : PHASE_PASSPHRASE; + const phase = (backupInfo && !force) ? PHASE_MIGRATE : PHASE_CHOOSE_KEY_PASSPHRASE; this.setState({ phase, @@ -152,6 +160,12 @@ export default class CreateSecretStorageDialog extends React.PureComponent { if (this.state.phase === PHASE_MIGRATE) this._fetchBackupInfo(); } + _onKeyPassphraseChange = e => { + this.setState({ + passPhraseKeySelected: e.target.value, + }); + } + _collectRecoveryKeyNode = (n) => { this._recoveryKeyNode = n; } @@ -162,6 +176,24 @@ export default class CreateSecretStorageDialog extends React.PureComponent { }); } + _onChooseKeyPassphraseFormSubmit = async () => { + if (this.state.passPhraseKeySelected === CREATESTORAGE_OPTION_KEY) { + this._recoveryKey = + await MatrixClientPeg.get().createRecoveryKeyFromPassphrase(); + this.setState({ + copied: false, + downloaded: false, + phase: PHASE_SHOWKEY, + }); + } else { + this.setState({ + copied: false, + downloaded: false, + phase: PHASE_PASSPHRASE, + }); + } + } + _onMigrateFormSubmit = (e) => { e.preventDefault(); if (this.state.backupSigStatus.usable) { @@ -427,6 +459,53 @@ export default class CreateSecretStorageDialog extends React.PureComponent { }); } + _renderPhaseChooseKeyPassphrase() { + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); + return
+

{_t( + "Safeguard against losing access to encrypted messages & data by " + + "backing up encryption keys on your server.", + )}

+
+ +
+ + {_t("Generate a Security Key")} +
+
{_t("We’ll generate a Security Key for you to store somewhere safe, like a password manager or a safe.")}
+
+ +
+ + {_t("Enter a Security Phrase")} +
+
{_t("Use a secret phrase only you know, and optionally save a Security Key to use for backup.")}
+
+
+ + ; + } + _renderPhaseMigrate() { // TODO: This is a temporary screen so people who have the labs flag turned on and // click the button are aware they're making a change to their account. @@ -716,6 +795,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent { _titleForPhase(phase) { switch (phase) { + case PHASE_CHOOSE_KEY_PASSPHRASE: + return _t('Set up Secure backup'); case PHASE_MIGRATE: return _t('Upgrade your encryption'); case PHASE_PASSPHRASE: @@ -760,6 +841,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent { case PHASE_LOADERROR: content = this._renderPhaseLoadError(); break; + case PHASE_CHOOSE_KEY_PASSPHRASE: + content = this._renderPhaseChooseKeyPassphrase(); + break; case PHASE_MIGRATE: content = this._renderPhaseMigrate(); break; diff --git a/src/components/views/elements/StyledRadioButton.tsx b/src/components/views/elements/StyledRadioButton.tsx index d7ae4d5af8..3b83666048 100644 --- a/src/components/views/elements/StyledRadioButton.tsx +++ b/src/components/views/elements/StyledRadioButton.tsx @@ -42,7 +42,7 @@ export default class StyledRadioButton extends React.PureComponent {/* Used to render the radio button circle */}
- {children} +
{children}
; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 646f43af33..c7b05eafe4 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2171,6 +2171,11 @@ "Import": "Import", "Confirm encryption setup": "Confirm encryption setup", "Click the button below to confirm setting up encryption.": "Click the button below to confirm setting up encryption.", + "Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.": "Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.", + "Generate a Security Key": "Generate a Security Key", + "We’ll generate a Security Key for you to store somewhere safe, like a password manager or a safe.": "We’ll generate a Security Key for you to store somewhere safe, like a password manager or a safe.", + "Enter a Security Phrase": "Enter a Security Phrase", + "Use a secret phrase only you know, and optionally save a Security Key to use for backup.": "Use a secret phrase only you know, and optionally save a Security Key to use for backup.", "Enter your account password to confirm the upgrade:": "Enter your account password to confirm the upgrade:", "Restore your key backup to upgrade your encryption": "Restore your key backup to upgrade your encryption", "Restore": "Restore", @@ -2200,6 +2205,7 @@ "Unable to query secret storage status": "Unable to query secret storage status", "Retry": "Retry", "You can now verify your other devices, and other users to keep your chats safe.": "You can now verify your other devices, and other users to keep your chats safe.", + "Set up Secure backup": "Set up Secure backup", "Upgrade your encryption": "Upgrade your encryption", "Confirm recovery passphrase": "Confirm recovery passphrase", "Make a copy of your recovery key": "Make a copy of your recovery key", From 059d5f927d549a6ebb22e6adbbbd3144814a3b31 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 23 Jun 2020 15:06:52 +0100 Subject: [PATCH 043/294] Prepare changelog for v2.8.0 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d7a73b264..21bfbf0a93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +Changes in [2.8.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.8.0) (2020-06-23) +=================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.8.0-rc.1...v2.8.0) + + * Update read receipt remainder for internal font size change + [\#4807](https://github.com/matrix-org/matrix-react-sdk/pull/4807) + * Revert "Use recovery keys over passphrases" + [\#4793](https://github.com/matrix-org/matrix-react-sdk/pull/4793) + Changes in [2.8.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.8.0-rc.1) (2020-06-17) ============================================================================================================= [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.7.2...v2.8.0-rc.1) From 5256a86545d601a4579024f5650ce16ddc2d2f87 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 23 Jun 2020 15:06:53 +0100 Subject: [PATCH 044/294] v2.8.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 07cb11d45b..591922498f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "2.8.0-rc.1", + "version": "2.8.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From f3af6031028f0718450bb10d8e067e4504667674 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 23 Jun 2020 15:19:55 +0100 Subject: [PATCH 045/294] 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 591922498f..673ea34a68 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.0.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 2aa39a257f..c20658f014 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.0.0: +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": version "7.0.0" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-7.0.0.tgz#da2b24e57574379c3d8f7065eb68ea6c479d9806" - integrity sha512-0i1NmfwS5HzEPPjqUAXpw81o+8DImBS67QQBemJiM6D/imU3KFBacdhkmgjMLKXnAfTy6a+aCGfYBfVolfmNQw== + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/f683f4544aa5da150836b01c754062809119fa97" dependencies: "@babel/runtime" "^7.8.3" another-json "^0.2.0" From 07cbf23b22a6cb1dcebf8ce9c96dc1b64c493b3b Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Tue, 23 Jun 2020 00:23:58 +0000 Subject: [PATCH 046/294] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2288 of 2288 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index c89c049ae5..b5bfcb4875 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2527,5 +2527,11 @@ "Upgrade your Recovery Key": "升級您的復原金鑰", "Store your Recovery Key": "儲存您的復原金鑰", "Use the improved room list (in development - will refresh to apply changes)": "使用改進的聊天室清單(開發中 ── 將會重新整理以套用變更)", - "Use the improved room list (will refresh to apply changes)": "使用改進的聊天室清單(將會重新整理以套用變更)" + "Use the improved room list (will refresh to apply changes)": "使用改進的聊天室清單(將會重新整理以套用變更)", + "Enable IRC layout option in the appearance tab": "在外觀分頁中啟用 IRC 佈局選項", + "Use custom size": "使用自訂大小", + "Hey you. You're the best!": "你是最棒的!", + "Message layout": "訊息佈局", + "Compact": "簡潔", + "Modern": "現代" } From 876e7799baf217ee1572a94d8e35e6907a4aca3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Tue, 23 Jun 2020 10:27:01 +0000 Subject: [PATCH 047/294] Translated using Weblate (Estonian) Currently translated at 70.2% (1606 of 2288 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/et/ --- src/i18n/strings/et.json | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index afe752015a..32c069f6c1 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -1655,5 +1655,35 @@ "Failed to leave room": "Jututoast lahkumine ei õnnestunud", "Can't leave Server Notices room": "Serveriteadete jututoast ei saa lahkuda", "This room is used for important messages from the Homeserver, so you cannot leave it.": "Seda jututuba kasutatakse sinu koduserveri oluliste teadete jaoks ja seega sa ei saa sealt lahkuda.", - "Signed Out": "Välja logitud" + "Signed Out": "Välja logitud", + "A username can only contain lower case letters, numbers and '=_-./'": "Kasutajanimes võivad olla vaid väiketähed, numbrid ja need viis tähemärki =_-./", + "Username not available": "Selline kasutajanimi ei ole saadaval", + "Username invalid: %(errMessage)s": "Vigane kasutajanimi: %(errMessage)s", + "An error occurred: %(error_string)s": "Tekkis viga: %(error_string)s", + "Checking...": "Kontrollin...", + "Username available": "Kasutajanimi on saadaval", + "To get started, please pick a username!": "Alustamiseks palun vali kasutajanimi!", + "This will be your account name on the homeserver, or you can pick a different server.": "See saab olema sinu kasutajanimi koduserveris, aga sa võid valida ka mõne teise serveri.", + "If you already have a Matrix account you can log in instead.": "Kui sul juba on olemas Matrix'i konto, siis võid lihtsalt sisse logida.", + "You have successfully set a password!": "Salasõna loomine õnnestus!", + "You have successfully set a password and an email address!": "Salasõna loomine ja e-posti aadressi salvestamine õnnestus!", + "You can now return to your account after signing out, and sign in on other devices.": "Nüüd sa saad peale väljalogimist pöörduda tagasi oma konto juurde või logida sisse muudest seadmetest.", + "Remember, you can always set an email address in user settings if you change your mind.": "Jäta meelde, et sa saad alati hiljem määrata kasutajaseadetest oma e-posti aadressi.", + "Use bots, bridges, widgets and sticker packs": "Kasuta roboteid, võrgusildu, vidinaid või kleepsupakke", + "Upload all": "Lae kõik üles", + "This file is too large to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.": "See fail on üleslaadimiseks liiga suur. Üleslaetavate failide mahupiir on %(limit)s, kuid selle faili suurus on %(sizeOfThisFile)s.", + "Appearance": "Välimus", + "Enter recovery passphrase": "Sisesta taastamise paroolifraas", + "For security, this session has been signed out. Please sign in again.": "Turvalisusega seotud põhjustel on see sessioon välja logitud. Palun logi uuesti sisse.", + "Review terms and conditions": "Vaata üle kasutustingimused", + "Old cryptography data detected": "Tuvastasin andmed, mille puhul on kasutatud vanemat tüüpi krüptimist", + "Self-verification request": "Päring enda verifitseerimiseks", + "Switch to light mode": "Kasuta heledat teemat", + "Switch to dark mode": "Kasuta tumedat teemat", + "Switch theme": "Vaheta teemat", + "Security & privacy": "Turvalisus ja privaatsus", + "All settings": "Kõik seadistused", + "Archived rooms": "Arhiveeritud jututoad", + "Feedback": "Tagasiside", + "Account settings": "Kasutajakonto seadistused" } From c5da3d726d919ddc07aefd63994bf972c0356bb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Tue, 23 Jun 2020 07:06:57 +0000 Subject: [PATCH 048/294] Translated using Weblate (French) Currently translated at 100.0% (2288 of 2288 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/fr/ --- src/i18n/strings/fr.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index 1651164a69..1a89f1be6d 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -2528,5 +2528,11 @@ "Upgrade your Recovery Key": "Mettre à jour votre clé de récupération", "Store your Recovery Key": "Stocker votre clé de récupération", "Use the improved room list (in development - will refresh to apply changes)": "Utiliser la liste de salons améliorée (en développement − actualisera pour appliquer les changements)", - "Use the improved room list (will refresh to apply changes)": "Utiliser la liste de salons améliorée (actualisera pour appliquer les changements)" + "Use the improved room list (will refresh to apply changes)": "Utiliser la liste de salons améliorée (actualisera pour appliquer les changements)", + "Enable IRC layout option in the appearance tab": "Activer l’option de mise en page IRC dans l’onglet d’apparence", + "Use custom size": "Utiliser une taille personnalisée", + "Hey you. You're the best!": "Eh vous. Vous êtes les meilleurs !", + "Message layout": "Mise en page des messages", + "Compact": "Compacte", + "Modern": "Moderne" } From 6255c67d4f0ddcc5447b111d9454acaceded2cfb Mon Sep 17 00:00:00 2001 From: random Date: Tue, 23 Jun 2020 09:15:58 +0000 Subject: [PATCH 049/294] Translated using Weblate (Italian) Currently translated at 99.9% (2287 of 2288 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/it/ --- src/i18n/strings/it.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index 6f213de237..bf54e484bc 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -2522,5 +2522,11 @@ "Create a Recovery Key": "Crea una chiave di recupero", "Upgrade your Recovery Key": "Aggiorna la chiave di recupero", "Store your Recovery Key": "Salva la chiave di recupero", - "Use the improved room list (will refresh to apply changes)": "Usa l'elenco stanze migliorato (verrà ricaricato per applicare le modifiche)" + "Use the improved room list (will refresh to apply changes)": "Usa l'elenco stanze migliorato (verrà ricaricato per applicare le modifiche)", + "Enable IRC layout option in the appearance tab": "Attiva l'opzione per il layout IRC nella scheda dell'aspetto", + "Use custom size": "Usa dimensione personalizzata", + "Hey you. You're the best!": "Ehi tu. Sei il migliore!", + "Message layout": "Layout messaggio", + "Compact": "Compatto", + "Modern": "Moderno" } From 84bcd98d1763207bffee1e1e41509df7b8594efd Mon Sep 17 00:00:00 2001 From: ziriSut Date: Tue, 23 Jun 2020 05:08:20 +0000 Subject: [PATCH 050/294] Translated using Weblate (Kabyle) Currently translated at 19.6% (449 of 2288 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/kab/ --- src/i18n/strings/kab.json | 220 ++++++++++++++++++++++++-------------- 1 file changed, 142 insertions(+), 78 deletions(-) diff --git a/src/i18n/strings/kab.json b/src/i18n/strings/kab.json index 285fb6cbf1..ad1b2ed516 100644 --- a/src/i18n/strings/kab.json +++ b/src/i18n/strings/kab.json @@ -80,7 +80,7 @@ "Bicycle": "Azlalam", "Ball": "Balles", "Anchor": "Tamdeyt", - "Headphones": "", + "Headphones": "Wennez", "Folder": "Akaram", "Upload": "Sali", "Remove": "Sfeḍ", @@ -100,9 +100,9 @@ "Keywords": "Awalen tisura", "Clear notifications": "Sfeḍ ilɣuyen", "Off": "Insa", - "Display Name": "", + "Display Name": "Sken isem", "Save": "Sekles", - "Disconnect": "", + "Disconnect": "Ffeɣ seg tuqqna", "Go back": "Uɣal ɣer deffir", "Change": "Beddel", "Theme": "Asentel", @@ -117,17 +117,17 @@ "Keyboard Shortcuts": "Inegzumen n unasiw", "Versions": "Ileqman", "None": "Ula yiwen", - "Unsubscribe": "", + "Unsubscribe": "Sefsex ajerred", "Ignore": "Ttu", "Subscribe": "Jerred", - "Preferences": "", - "Composer": "", + "Preferences": "Ismenyifen", + "Composer": "Imsuddes", "Timeline": "Amazray", "Microphone": "Asawaḍ", "Camera": "Takamiṛatt", - "Sounds": "Imeslan", - "Reset": "Ales awennez", - "Browse": "Snirem", + "Sounds": "Imesla", + "Reset": "Wennez", + "Browse": "Inig", "Default role": "Tamlilt tamzwert", "Permissions": "Tisirag", "Anyone": "Yal yiwen", @@ -136,38 +136,38 @@ "Share": "Bḍu", "Verification code": "Tangalt n usenqed", "Add": "Rnu", - "Email Address": "Tansa imayl", + "Email Address": "Tansa n yimayl", "Phone Number": "Uṭṭun n tiliɣri", - "Upload file": "Azen afaylu", + "Upload file": "Sali-d afaylu", "Bold": "Azuran", - "Strikethrough": "Jerreḍ", - "Quote": "Citation", - "Loading...": "Yessalay-ed…", - "Idle": "Idle", - "Unknown": "D arussin", + "Strikethrough": "Derrer", + "Quote": "Tanebdurt", + "Loading...": "La d-yettali…", + "Idle": "Arurmid", + "Unknown": "Arussin", "Settings": "Iɣewwaren", "Search": "Nadi", "Favourites": "Ismenyifen", "People": "Imdanen", "Sign Up": "Jerred", - "Reject": "Aggi", + "Reject": "Agi", "Not now": "Mačči tura", - "Sort by": "Smizzwer s", + "Sort by": "Semyizwer s", "Activity": "Armud", "A-Z": "A-Z", "Show": "Sken", - "Options": "Tinefrunin", + "Options": "Tixtiṛiyin", "Server error": "Tuccḍa n uqeddac", - "Members": "Imedrawen", + "Members": "Imettekkiyen", "Files": "Ifuyla", - "Trusted": "De confiance", - "Invite": "Nced…", - "Unmute": "Susem", - "Mute": "Kkes imesli", - "Are you sure?": "Tebɣiḍ ?", + "Trusted": "Yettwattkal", + "Invite": "Nced", + "Unmute": "Rmed imesli", + "Mute": "Sens imesli", + "Are you sure?": "Tebɣiḍ s tidet?", "Security": "Taɣellist", "Yes": "Ih", - "Verified": "Verified", + "Verified": "Yettwasenqed", "Got it": "Awi-t", "Sunday": "Acer", "Monday": "Arim", @@ -178,48 +178,48 @@ "Saturday": "Sed", "Today": "Ass-a", "Yesterday": "Iḍelli", - "View Source": "Sken aɣbalu", + "View Source": "Wali aɣbalu", "Reply": "Err", "Edit": "Ẓreg", - "Attachment": "Attachement", - "Error decrypting attachment": "Tuccḍa deg uzmak n ufaylu yeddan", + "Attachment": "Taceqquft yeddan", + "Error decrypting attachment": "Tuccḍa deg uwgelhen n tceqquft yeddan", "Show all": "Sken akk", - "Message deleted": "Izen yettwakkes", - "Copied!": "Yettusukken!", - "edited": "yeẓreg", - "Food & Drink": "Učči aked tissit", + "Message deleted": "Izen yettwakksen", + "Copied!": "Yettwanɣel!", + "edited": "yettwaẓreg", + "Food & Drink": "Učči d tissit", "Objects": "Tiɣawsiwin", "Symbols": "Izamulen", "Flags": "Anayen", "Categories": "Taggayin", - "More options": "Ugar n textirin", - "Join": "Semlil", + "More options": "Ugar n textiṛiyin", + "Join": "Rnu", "No results": "Ulac igmad", - "collapse": "sneḍfes", - "Rotate counter-clockwise": "Zzi di tnila tanemgalt n tsegnatin n temrilt", - "Rotate clockwise": "Zzi di tnila n tsegnatin n temrilt", + "collapse": "fneẓ", + "Rotate counter-clockwise": "Zzi mgal tanila n tessegnatin n temrilt", + "Rotate clockwise": "Zzi almend n tnila n tsegnatin n temrilt", "Add User": "Rnu aseqdac", "Server name": "Isem n uqeddac", - "email address": "tansa imayl", - "Close dialog": "Mdel tanaka n usdiwen", - "Notes": "Tamawt", - "Unavailable": "Ulac-it", - "Changelog": "Aɣmis n ibeddilen", + "email address": "tansa n yimayl", + "Close dialog": "Mdel adiwenni", + "Notes": "Tamawin", + "Unavailable": "Ulac", + "Changelog": "Aɣmis n yisnifal", "Removing…": "Tukksa…", - "Confirm Removal": "Serggeg tukksa", + "Confirm Removal": "Sentem tukksa", "Example": "Amedya", "example": "amedya", "Create": "Snulfu-d", "Name": "Isem", - "Sign out": "Ffeɣ", - "Back": "Retour", + "Sign out": "Ffeɣ seg tuqqna", + "Back": "Uɣal ɣer deffir", "Send": "Azen", "Suggestions": "Isumar", "Go": "Ddu", "Session name": "Isem n tɣimit", "Send report": "Azen aneqqis", - "Refresh": "Sismeḍ", - "Email address": "Tansa email", + "Refresh": "Smiren", + "Email address": "Tansa n yimayl", "Skip": "Zgel", "Username not available": "Ulac isem n useqdac", "Checking...": "Asenqed...", @@ -227,21 +227,21 @@ "Terms of Service": "Tiwtilin n useqdec", "Service": "Ameẓlu", "Summary": "Agzul", - "Document": "isemli", - "Next": "Ar zdat", - "Upload files": "Azen ifuyla", - "Appearance": "Udem", + "Document": "Isemli", + "Next": "Γer sdat", + "Upload files": "Sali-d ifuyla", + "Appearance": "Arwes", "Allow": "Sireg", - "Deny": "Agwi", - "Custom": "Personnalisé", - "Source URL": "URL aγbalu", - "Notification settings": "Iɣewwaṛen n yilɣa", + "Deny": "Agi", + "Custom": "Sagen", + "Source URL": "URL aɣbalu", + "Notification settings": "Iɣewwaren n yilɣa", "Leave": "Ffeɣ", - "Set status": "Sbadu addad", + "Set status": "Sbadu addaden", "Hide": "Ffer", "Home": "Agejdan", "Sign in": "Qqen", - "Help": "Tallelt", + "Help": "Tallalt", "Reload": "Smiren", "powered by Matrix": "s lmendad n Matrix", "Custom Server Options": "Iɣewwaren n uqeddac udmawan", @@ -250,40 +250,40 @@ "Email": "Imayl", "Username": "Isem n useqdac", "Phone": "Tiliɣri", - "Passwords don't match": "Awal uffiren ur menṭaḍen ara", + "Passwords don't match": "Awalen uffiren ur mṣadan ara", "Email (optional)": "Imayl (Afrayan)", "Register": "Jerred", "Free": "Ilelli", - "Failed to upload image": "Ur yezmir ad yessali tugna", - "Description": "Aseglem", + "Failed to upload image": "Tegguma ad d-tali tugna", + "Description": "Aglam", "Explore": "Snirem", "Filter": "Imsizdeg", "Explore rooms": "Snirem tixxamin", - "Unknown error": "Erreur inconnue", - "Logout": "Tufɣa", - "Preview": "Timeẓriwt", - "View": "Ɣeṛ", - "Guest": "Inebgi", - "Your profile": "Amaɣnu-ik", - "Feedback": "Tikti", - "Your password has been reset.": "Awal n uɛeddi inek yules awennez.", - "Syncing...": "Amtawi", + "Unknown error": "Tuccḍa tarussint", + "Logout": "Tuffɣa", + "Preview": "Taskant", + "View": "Sken", + "Guest": "Anerzaf", + "Your profile": "Amaɣnu-ik/im", + "Feedback": "Takti", + "Your password has been reset.": "Awal uffir-inek/inem yettuwennez.", + "Syncing...": "Amtawi...", "Create account": "Rnu amiḍan", - "Create your account": "Rnu amiḍan-ik", - "Go Back": "Précédent", + "Create your account": "Rnu amiḍan-ik/im", + "Go Back": "Uɣal ɣer deffir", "Commands": "Tiludna", "Users": "Iseqdacen", "Export": "Sifeḍ", "Import": "Kter", "Restore": "Err-d", - "Copy": "Nγel", - "Download": "Sider", - "Retry": "Ɛreḍ tikkelt nniḍen", + "Copy": "Nɣel", + "Download": "Sader", + "Retry": "Ɛreḍ tikkelt-nniḍen", "Starting backup...": "Asenker n uḥraz...", "Success!": "Akka d rrbeḥ !", - "Disable": "Désactiver", + "Disable": "Sens", "Navigation": "Tunigin", - "Calls": "Appels", + "Calls": "isawalen", "Alt": "Alt", "Shift": "Shift", "New line": "Izirig amaynut", @@ -386,5 +386,69 @@ "Invite to Community": "Ɛreḍ-d ɣer temɣiwent", "Room name or address": "Isem neɣ tansa n texxamt", "Add to community": "Rnu ɣer temɣiwent", - "Unnamed Room": "Taxxamt war isem" + "Unnamed Room": "Taxxamt war isem", + "The server does not support the room version specified.": "Aqeddac ur issefrek ara lqem n texxamt yettwafernen.", + "If you cancel now, you won't complete verifying the other user.": "Ma yella teffɣeḍ tura, asenqed n yiseqdacen-nniḍen ur ittemmed ara.", + "Cancel entering passphrase?": "Sefsex tafyirt tuffirt n uεeddi?", + "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Γur-k: yal amdan ara ternuḍ ɣer temɣiwent ad d-iban s wudem azayaz i yal yiwen yessnen asulay n temɣiwent", + "Invite new community members": "Nced-d imttekkiyen imaynuten ɣer temɣiwent", + "Which rooms would you like to add to this community?": "Anti tixxamin i tebɣiḍ adternuḍ i temɣiwent-a?", + "Add rooms to the community": "Rnu tixxamin ɣer temɣiwent", + "Failed to invite the following users to %(groupId)s:": "Ancad n yiseqdacen i d-iteddun %(groupId)s ur yeddi ara:", + "Failed to invite users to community": "Ancad n yiseqdacen ɣer temɣiwent ur yeddi ara", + "Failed to invite users to %(groupId)s": "Ancad n yiseqdacen ɣer %(groupId)s ur yedi ara", + "Failed to add the following rooms to %(groupId)s:": "Timerna n texxamin i d-iteddun ɣer %(groupId)s ur yedi ara:", + "Identity server has no terms of service": "Timagit n uqeddac ulac ɣer-sen iferdisen n umeẓlu", + "%(name)s is requesting verification": "%(name)s yesra asenqed", + "Riot does not have permission to send you notifications - please check your browser settings": "Riot ulac ɣer-s tisirag i tuzna n yilɣa - ttxil-k/m senqed iɣewwaren n yiminig-ik/im", + "Riot was not given permission to send notifications - please try again": "Riot ur d-yefk ara tisirag i tuzna n yilɣa - ttxil-k/m εreḍ tikkelt-nniḍen", + "Unable to enable Notifications": "Sens irmad n yilɣa", + "This email address was not found": "Tansa-a n yimayl ulac-it", + "Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Tansa-ik/im n yimayl ur d-tban ara akken ad tettwacudd d usulay Matrix deg usebter-a agejdan.", + "Sign In or Create Account": "Kcem ɣer neɣ rnu amiḍan", + "Use your account or create a new one to continue.": "Seqdec amiḍan-ik/im neɣ snulfu-d yiwen akken ad tkemmleḍ.", + "Custom (%(level)s)": "Sagen (%(level)s)", + "Failed to invite": "Ulamek i d-tnecdeḍ", + "You need to be able to invite users to do that.": "Tesriḍ ad tizmireḍ ad d-tnecdeḍ iseqdacen ad gen ayagi.", + "Failed to send request.": "Tuzna n usuter ur teddi ara.", + "This room is not recognised.": "Taxxamt-a ur tṣeggem ara.", + "You are not in this room.": "Ulac-ik/ikem deg texxamt-a.", + "You do not have permission to do that in this room.": "Ur tesεiḍ ara tasiregt ad tgeḍ ayagi deg texxamt-a.", + "Missing room_id in request": "Ixuṣṣ taxxamt_asulay deg usuter", + "Room %(roomId)s not visible": "Taxxamt %(roomId)s ur d-tban ara", + "Missing user_id in request": "Ixuṣṣ useqdac_asulay deg usuter", + "Command error": "Tuccḍa n tladna", + "/ddg is not a command": "/ddg mačči d taladna", + "Upgrades a room to a new version": "Leqqem taxxamt ɣer lqem amaynut", + "Error upgrading room": "Tuccḍa deg uleqqem n texxamt", + "Changes your avatar in this current room only": "Snifel avatar-ik/im deg texxamat-agi kan tamirant", + "Changes your avatar in all rooms": "Snifel avatar/ik/om deg yixxamin", + "Use an identity server": "Seqdec timagit n uqeddac", + "Joins room with given address": "Kcem ɣer texxamt s tansa i d-yettunefken", + "Leave room": "Ffeɣ seg texxamt", + "Ignores a user, hiding their messages from you": "Anef iuseqdac, ffer iznan-ines sɣur-k", + "Ignored user": "Aseqdac yettunfen", + "You are now ignoring %(userId)s": "Aql-ak tura tunfeḍ i %(userId)s", + "Command failed": "Taladna ur teddi ara", + "Could not find user in room": "Ur yettwaf ara useqdac deg texxamt", + "(no answer)": "(ulac tiririt)", + "New login. Was this you?": "Anekcam amaynut. D kečč/kemm?", + "Verify the new login accessing your account: %(name)s": "Senqed anekcam amaynut i ikecmen ɣer umiḍan-ik/im: %(name)s", + "What's new?": "D acu-t umaynut?", + "Upgrade your Riot": "Leqqem Riot inek/inem", + "A new version of Riot is available!": "Lqem amaynut n Riot yella!", + "You: %(message)s": "Kečč/kemm: %(message)s", + "There was an error joining the room": "Tella-d tuccḍa deg unekcum ɣer texxamt", + "Sorry, your homeserver is too old to participate in this room.": "Suref-aɣ, asebter-ik/im agejdan d aqbur aṭas akken ad yettekki deg texxamt-a.", + "Please contact your homeserver administrator.": "Ttxil-k/m nermes anedbal-ik/im n usebter agejdan.", + "Failed to join room": "Anekcum ɣer texxamt ur yeddi ara", + "Custom user status messages": "Sagen addaden n yiznan n useqdac", + "Support adding custom themes": "Tallalt n tmerna n yisental udmawanen", + "Use custom size": "Seqdec teɣzi tudmawant", + "Show avatar changes": "Sken isnifal n avatar", + "Always show encryption icons": "Sken yal tikkelt tignitin tiwgelhanen", + "Send typing notifications": "Azen ilɣa yettuszemlen", + "Show typing notifications": "Azen ilɣa yettuszemlen", + "Room Colour": "Initen n texxamt", + "Show developer tools": "Sken ifecka n uneflay" } From 38293627545e8ab1b7f4245e94bcd377f298ca10 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 23 Jun 2020 15:24:02 +0100 Subject: [PATCH 051/294] Fix up merge to develop --- src/components/structures/auth/SetupEncryptionBody.js | 8 ++++++++ src/stores/SetupEncryptionStore.js | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/structures/auth/SetupEncryptionBody.js b/src/components/structures/auth/SetupEncryptionBody.js index 6b30f3c084..f2e702c8cb 100644 --- a/src/components/structures/auth/SetupEncryptionBody.js +++ b/src/components/structures/auth/SetupEncryptionBody.js @@ -28,6 +28,14 @@ import { PHASE_FINISHED, } from '../../../stores/SetupEncryptionStore'; +function keyHasPassphrase(keyInfo) { + return ( + keyInfo.passphrase && + keyInfo.passphrase.salt && + keyInfo.passphrase.iterations + ); +} + export default class SetupEncryptionBody extends React.Component { static propTypes = { onFinished: PropTypes.func.isRequired, diff --git a/src/stores/SetupEncryptionStore.js b/src/stores/SetupEncryptionStore.js index 2badf3bc33..63b8c428eb 100644 --- a/src/stores/SetupEncryptionStore.js +++ b/src/stores/SetupEncryptionStore.js @@ -36,7 +36,7 @@ export class SetupEncryptionStore extends EventEmitter { return; } this._started = true; - this.phase = PHASE_INTRO; + this.phase = PHASE_BUSY; this.verificationRequest = null; this.backupInfo = null; From 6739c90f442450f4cf036e777bb66cbbe32cbe0b Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 23 Jun 2020 15:25:59 +0100 Subject: [PATCH 052/294] Fix SDK in changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21bfbf0a93..964e75f7a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Changes in [2.8.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v =================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.8.0-rc.1...v2.8.0) + * Upgrade to JS SDK 7.0.0 * Update read receipt remainder for internal font size change [\#4807](https://github.com/matrix-org/matrix-react-sdk/pull/4807) * Revert "Use recovery keys over passphrases" From 0694776b255da1a1da8cf6644273e590dee47e3d Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 23 Jun 2020 16:27:41 +0100 Subject: [PATCH 053/294] Update the 'save your security key' screen --- .../_CreateSecretStorageDialog.scss | 35 ++++--- .../CreateSecretStorageDialog.js | 99 ++++++------------- src/i18n/strings/en_EN.json | 22 +++-- 3 files changed, 66 insertions(+), 90 deletions(-) diff --git a/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss b/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss index 28df5039f1..0d06d503b0 100644 --- a/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss +++ b/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss @@ -91,33 +91,42 @@ limitations under the License. margin-left: 20px; } -.mx_CreateSecretStorageDialog_recoveryKeyHeader { - margin-bottom: 1em; -} - .mx_CreateSecretStorageDialog_recoveryKeyContainer { - display: flex; + width: 380px; + margin-left: auto; + margin-right: auto; } .mx_CreateSecretStorageDialog_recoveryKey { - width: 262px; + font-weight: bold; + text-align: center; padding: 20px; color: $info-plinth-fg-color; background-color: $info-plinth-bg-color; - margin-right: 12px; + border-radius: 6px; + word-spacing: 1em; + margin-bottom: 20px; } .mx_CreateSecretStorageDialog_recoveryKeyButtons { - flex: 1; display: flex; + justify-content: space-between; align-items: center; } .mx_CreateSecretStorageDialog_recoveryKeyButtons .mx_AccessibleButton { - margin-right: 10px; -} - -.mx_CreateSecretStorageDialog_recoveryKeyButtons button { - flex: 1; + width: 160px; + padding-left: 0px; + padding-right: 0px; white-space: nowrap; } + +.mx_CreateSecretStorageDialog_continueSpinner { + margin-top: 33px; + text-align: right; +} + +.mx_CreateSecretStorageDialog_continueSpinner img { + width: 20px; + height: 20px; +} diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index 08a1dc5d9e..7175f27e9f 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -27,6 +27,9 @@ import {copyNode} from "../../../../utils/strings"; import {SSOAuthEntry} from "../../../../components/views/auth/InteractiveAuthEntryComponents"; import PassphraseField from "../../../../components/views/auth/PassphraseField"; import StyledRadioButton from '../../../../components/views/elements/StyledRadioButton'; +import AccessibleButton from "../../../../components/views/elements/AccessibleButton"; +import DialogButtons from "../../../../components/views/elements/DialogButtons"; +import InlineSpinner from "../../../../components/views/elements/InlineSpinner"; const PHASE_LOADING = 0; const PHASE_LOADERROR = 1; @@ -35,7 +38,6 @@ const PHASE_MIGRATE = 3; const PHASE_PASSPHRASE = 4; const PHASE_PASSPHRASE_CONFIRM = 5; const PHASE_SHOWKEY = 6; -const PHASE_KEEPITSAFE = 7; const PHASE_STORING = 8; const PHASE_DONE = 9; const PHASE_CONFIRM_SKIP = 10; @@ -208,7 +210,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent { if (successful) { this.setState({ copied: true, - phase: PHASE_KEEPITSAFE, }); } } @@ -221,7 +222,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent { this.setState({ downloaded: true, - phase: PHASE_KEEPITSAFE, }); } @@ -374,6 +374,10 @@ export default class CreateSecretStorageDialog extends React.PureComponent { this._fetchBackupInfo(); } + _onShowKeyContinueClick = () => { + this._bootstrapSecretStorage(); + } + _onSkipSetupClick = () => { this.setState({phase: PHASE_CONFIRM_SKIP}); } @@ -429,12 +433,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent { }); } - _onKeepItSafeBackClick = () => { - this.setState({ - phase: PHASE_SHOWKEY, - }); - } - _onPassPhraseValidate = (result) => { this.setState({ passPhraseValid: result.valid, @@ -460,8 +458,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent { } _renderPhaseChooseKeyPassphrase() { - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); - const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); return

{_t( "Safeguard against losing access to encrypted messages & data by " + @@ -512,7 +508,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent { // Once we're confident enough in this (and it's supported enough) we can do // it automatically. // https://github.com/vector-im/riot-web/issues/11696 - const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const Field = sdk.getComponent('views.elements.Field'); let authPrompt; @@ -561,8 +556,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent { } _renderPhasePassPhrase() { - const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const LabelledToggleSwitch = sdk.getComponent('views.elements.LabelledToggleSwitch'); return @@ -614,7 +607,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent { } _renderPhasePassPhraseConfirm() { - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const Field = sdk.getComponent('views.elements.Field'); let matchText; @@ -645,7 +637,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent {

; } - const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); return

{_t( "Enter your recovery passphrase a second time to confirm it.", @@ -679,66 +670,48 @@ export default class CreateSecretStorageDialog extends React.PureComponent { } _renderPhaseShowKey() { - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + let continueButton; + if (this.state.phase === PHASE_SHOWKEY) { + continueButton = ; + } else { + continueButton =

+ +
; + } return

{_t( - "Your recovery key is a safety net - you can use it to restore " + - "access to your encrypted messages if you forget your recovery passphrase.", - )}

-

{_t( - "Keep a copy of it somewhere secure, like a password manager or even a safe.", + "Store your Security Key somewhere safe, like a password manager or a safe, " + + "as it’s used to safeguard your encrypted data.", )}

-
- {_t("Your recovery key")} -
{this._recoveryKey.encodedPrivateKey}
+ + {_t("Download")} + + {_t("or")} - {_t("Copy")} - - - {_t("Download")} + {this.state.copied ? _t("Copied!") : _t("Copy")}
-
; - } - - _renderPhaseKeepItSafe() { - let introText; - if (this.state.copied) { - introText = _t( - "Your recovery key has been copied to your clipboard, paste it to:", - {}, {b: s => {s}}, - ); - } else if (this.state.downloaded) { - introText = _t( - "Your recovery key is in your Downloads folder.", - {}, {b: s => {s}}, - ); - } - const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); - return
- {introText} -
    -
  • {_t("Print it and store it somewhere safe", {}, {b: s => {s}})}
  • -
  • {_t("Save it on a USB key or backup drive", {}, {b: s => {s}})}
  • -
  • {_t("Copy it to your personal cloud storage", {}, {b: s => {s}})}
  • -
- - - + {continueButton}
; } @@ -750,7 +723,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent { } _renderPhaseLoadError() { - const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); return

{_t("Unable to query secret storage status")}

@@ -764,7 +736,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent { } _renderPhaseDone() { - const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); return

{_t( "You can now verify your other devices, " + @@ -778,7 +749,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent { } _renderPhaseSkipConfirm() { - const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); return

{_t( "Without completing security on this session, it won’t have " + @@ -806,8 +776,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { case PHASE_CONFIRM_SKIP: return _t('Are you sure?'); case PHASE_SHOWKEY: - case PHASE_KEEPITSAFE: - return _t('Make a copy of your recovery key'); + return _t('Save your Security Key'); case PHASE_STORING: return _t('Setting up keys'); case PHASE_DONE: @@ -822,7 +791,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent { let content; if (this.state.error) { - const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); content =

{_t("Unable to set up secret storage")}

@@ -856,9 +824,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent { case PHASE_SHOWKEY: content = this._renderPhaseShowKey(); break; - case PHASE_KEEPITSAFE: - content = this._renderPhaseKeepItSafe(); - break; case PHASE_STORING: content = this._renderBusyPhase(); break; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index c7b05eafe4..385125a33d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2192,33 +2192,35 @@ "Go back to set it again.": "Go back to set it again.", "Enter your recovery passphrase a second time to confirm it.": "Enter your recovery passphrase a second time to confirm it.", "Confirm your recovery passphrase": "Confirm your recovery passphrase", - "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.": "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.", - "Keep a copy of it somewhere secure, like a password manager or even a safe.": "Keep a copy of it somewhere secure, like a password manager or even a safe.", - "Your recovery key": "Your recovery key", - "Copy": "Copy", + "Store your Security Key somewhere safe, like a password manager or a safe, as it’s used to safeguard your encrypted data.": "Store your Security Key somewhere safe, like a password manager or a safe, as it’s used to safeguard your encrypted data.", "Download": "Download", - "Your recovery key has been copied to your clipboard, paste it to:": "Your recovery key has been copied to your clipboard, paste it to:", - "Your recovery key is in your Downloads folder.": "Your recovery key is in your Downloads folder.", - "Print it and store it somewhere safe": "Print it and store it somewhere safe", - "Save it on a USB key or backup drive": "Save it on a USB key or backup drive", - "Copy it to your personal cloud storage": "Copy it to your personal cloud storage", + "Copy": "Copy", "Unable to query secret storage status": "Unable to query secret storage status", "Retry": "Retry", "You can now verify your other devices, and other users to keep your chats safe.": "You can now verify your other devices, and other users to keep your chats safe.", "Set up Secure backup": "Set up Secure backup", "Upgrade your encryption": "Upgrade your encryption", "Confirm recovery passphrase": "Confirm recovery passphrase", - "Make a copy of your recovery key": "Make a copy of your recovery key", + "Save your Security Key": "Save your Security Key", "You're done!": "You're done!", "Unable to set up secret storage": "Unable to set up secret storage", "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.": "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.", "For maximum security, this should be different from your account password.": "For maximum security, this should be different from your account password.", "Please enter your recovery passphrase a second time to confirm.": "Please enter your recovery passphrase a second time to confirm.", "Repeat your recovery passphrase...": "Repeat your recovery passphrase...", + "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.": "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.", + "Keep a copy of it somewhere secure, like a password manager or even a safe.": "Keep a copy of it somewhere secure, like a password manager or even a safe.", + "Your recovery key": "Your recovery key", + "Your recovery key has been copied to your clipboard, paste it to:": "Your recovery key has been copied to your clipboard, paste it to:", + "Your recovery key is in your Downloads folder.": "Your recovery key is in your Downloads folder.", + "Print it and store it somewhere safe": "Print it and store it somewhere safe", + "Save it on a USB key or backup drive": "Save it on a USB key or backup drive", + "Copy it to your personal cloud storage": "Copy it to your personal cloud storage", "Your keys are being backed up (the first backup could take a few minutes).": "Your keys are being backed up (the first backup could take a few minutes).", "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another session.": "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another session.", "Set up Secure Message Recovery": "Set up Secure Message Recovery", "Secure your backup with a recovery passphrase": "Secure your backup with a recovery passphrase", + "Make a copy of your recovery key": "Make a copy of your recovery key", "Starting backup...": "Starting backup...", "Success!": "Success!", "Create key backup": "Create key backup", From 3716f9d82c95a01639a9162d3674573c17768b1a Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 23 Jun 2020 16:43:52 +0100 Subject: [PATCH 054/294] Fix cancel button / prompt --- .../CreateSecretStorageDialog.js | 22 ++++++++++--------- src/i18n/strings/en_EN.json | 2 ++ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index 7175f27e9f..e81d017d7f 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -378,12 +378,12 @@ export default class CreateSecretStorageDialog extends React.PureComponent { this._bootstrapSecretStorage(); } - _onSkipSetupClick = () => { + _onCancelClick = () => { this.setState({phase: PHASE_CONFIRM_SKIP}); } - _onSetUpClick = () => { - this.setState({phase: PHASE_PASSPHRASE}); + _onGoBackClick = () => { + this.setState({phase: PHASE_CHOOSE_KEY_PASSPHRASE}); } _onSkipPassPhraseClick = async () => { @@ -496,7 +496,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { ; @@ -750,15 +750,17 @@ export default class CreateSecretStorageDialog extends React.PureComponent { _renderPhaseSkipConfirm() { return
- {_t( - "Without completing security on this session, it won’t have " + - "access to encrypted messages.", - )} +

{_t( + "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.", + )}

+

{_t( + "You can also set up Secure Backup & manage your keys in Settings.", + )}

- +
; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 385125a33d..0b7a261e45 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2198,6 +2198,8 @@ "Unable to query secret storage status": "Unable to query secret storage status", "Retry": "Retry", "You can now verify your other devices, and other users to keep your chats safe.": "You can now verify your other devices, and other users to keep your chats safe.", + "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.": "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.", + "You can also set up Secure Backup & manage your keys in Settings.": "You can also set up Secure Backup & manage your keys in Settings.", "Set up Secure backup": "Set up Secure backup", "Upgrade your encryption": "Upgrade your encryption", "Confirm recovery passphrase": "Confirm recovery passphrase", From 86597aabca88b538e4ec94cab3166704895f8567 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 23 Jun 2020 17:52:54 +0200 Subject: [PATCH 055/294] better naming --- src/theme.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/theme.js b/src/theme.js index 22cfd8b076..095990de3a 100644 --- a/src/theme.js +++ b/src/theme.js @@ -39,7 +39,7 @@ export function enumerateThemes() { function setCustomThemeVars(customTheme) { const {style} = document.body; - function setCSSVariable(name, hexColor, doPct = true) { + function setCSSColorVariable(name, hexColor, doPct = true) { style.setProperty(`--${name}`, hexColor); if (doPct) { // uses #rrggbbaa to define the color with alpha values at 0%, 15% and 50% @@ -53,10 +53,10 @@ function setCustomThemeVars(customTheme) { for (const [name, value] of Object.entries(customTheme.colors)) { if (Array.isArray(value)) { for (let i = 0; i < value.length; i += 1) { - setCSSVariable(`${name}_${i}`, value[i], false); + setCSSColorVariable(`${name}_${i}`, value[i], false); } } else { - setCSSVariable(name, value); + setCSSColorVariable(name, value); } } } From e083856801179eef1c6efb6452ea50b46d3d8f7e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 23 Jun 2020 17:53:16 +0200 Subject: [PATCH 056/294] allow changing the font-family --- res/themes/light-custom/css/_custom.scss | 2 ++ src/theme.js | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/res/themes/light-custom/css/_custom.scss b/res/themes/light-custom/css/_custom.scss index 6206496150..e7912e3cb0 100644 --- a/res/themes/light-custom/css/_custom.scss +++ b/res/themes/light-custom/css/_custom.scss @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +$font-family: var(--font-family, $font-family); +$monospace-font-family: var(--font-family-monospace, $monospace-font-family); // // --accent-color $accent-color: var(--accent-color); diff --git a/src/theme.js b/src/theme.js index 095990de3a..da70dc7bb1 100644 --- a/src/theme.js +++ b/src/theme.js @@ -60,6 +60,15 @@ function setCustomThemeVars(customTheme) { } } } + if (customTheme.fonts) { + const {fonts} = customTheme; + if (fonts.general) { + style.setProperty("--font-family", fonts.general); + } + if (fonts.monospace) { + style.setProperty("--font-family-monospace", fonts.monospace); + } + } } export function getCustomTheme(themeName) { From b3510aa2b47c3609810cbce2e4b2115b97291d0f Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 23 Jun 2020 17:54:17 +0200 Subject: [PATCH 057/294] remove css vars when switching theme --- src/theme.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/theme.js b/src/theme.js index da70dc7bb1..7e0eaabf11 100644 --- a/src/theme.js +++ b/src/theme.js @@ -35,6 +35,16 @@ export function enumerateThemes() { return Object.assign({}, customThemeNames, BUILTIN_THEMES); } +function clearCustomTheme() { + // remove all css variables, we assume these are there because of the custom theme + const inlineStyleProps = Object.values(document.body.style); + for (const prop of inlineStyleProps) { + if (prop.startsWith("--")) { + document.body.style.removeProperty(prop); + } + } +} + function setCustomThemeVars(customTheme) { const {style} = document.body; @@ -97,6 +107,7 @@ export async function setTheme(theme) { const themeWatcher = new ThemeWatcher(); theme = themeWatcher.getEffectiveTheme(); } + clearCustomTheme(); let stylesheetName = theme; if (theme.startsWith("custom-")) { const customTheme = getCustomTheme(theme.substr(7)); From 2f6fc6bba24140367c5ecdd18d40d611251d9997 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 23 Jun 2020 17:54:38 +0200 Subject: [PATCH 058/294] allow adding custom font faces in theme --- src/theme.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/theme.js b/src/theme.js index 7e0eaabf11..d560cc96c8 100644 --- a/src/theme.js +++ b/src/theme.js @@ -43,8 +43,23 @@ function clearCustomTheme() { document.body.style.removeProperty(prop); } } + const customFontFaceStyle = document.querySelector("head > style[title='custom-theme-font-faces']"); + if (customFontFaceStyle) { + customFontFaceStyle.remove(); + } } +function generateCustomFontFaceCSS(faces) { + return Object.entries(faces).map(([fontName, face]) => { + const src = Object.entries(face.src).map(([format, url]) => { + return `url('${url}') format('${format}')`; + }).join(", "); + return `@font-face {` + + ` font-family: '${fontName}';` + + ` src: ${src};` + + `}`; + }).join("\n"); +} function setCustomThemeVars(customTheme) { const {style} = document.body; @@ -72,6 +87,14 @@ function setCustomThemeVars(customTheme) { } if (customTheme.fonts) { const {fonts} = customTheme; + if (fonts.faces) { + const css = generateCustomFontFaceCSS(fonts.faces); + const style = document.createElement("style"); + style.setAttribute("title", "custom-theme-font-faces"); + style.setAttribute("type", "text/css"); + style.appendChild(document.createTextNode(css)); + document.head.appendChild(style); + } if (fonts.general) { style.setProperty("--font-family", fonts.general); } From 3b13a623cd755af6b196558d95515c428b8a1d83 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 23 Jun 2020 17:54:57 +0200 Subject: [PATCH 059/294] cleanup --- src/theme.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/theme.js b/src/theme.js index d560cc96c8..ee9cb9c7db 100644 --- a/src/theme.js +++ b/src/theme.js @@ -179,7 +179,7 @@ export async function setTheme(theme) { if (a == styleElements[stylesheetName]) return; a.disabled = true; }); - const bodyStyles = global.getComputedStyle(document.getElementsByTagName("body")[0]); + const bodyStyles = global.getComputedStyle(document.body); if (bodyStyles.backgroundColor) { document.querySelector('meta[name="theme-color"]').content = bodyStyles.backgroundColor; } From dd9112a01a926b39f363c49c0a384b9f6e450826 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 23 Jun 2020 11:44:40 -0600 Subject: [PATCH 060/294] Decrease margin between new sublists This is an attempt to increase density without adjusting the tiles directly. --- res/css/views/rooms/_RoomSublist2.scss | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss index 24151b15b0..c7dae56353 100644 --- a/res/css/views/rooms/_RoomSublist2.scss +++ b/res/css/views/rooms/_RoomSublist2.scss @@ -22,10 +22,12 @@ limitations under the License. flex-direction: column; margin-left: 8px; - margin-top: 12px; - margin-bottom: 12px; width: 100%; + &:first-child { + margin-top: 12px; // so we're not up against the search/filter + } + .mx_RoomSublist2_headerContainer { // Create a flexbox to make alignment easy display: flex; From cc60fff19848a5ddff74f7b369fcde421bc3b9b9 Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Tue, 23 Jun 2020 15:15:02 +0000 Subject: [PATCH 061/294] Translated using Weblate (Albanian) Currently translated at 99.9% (2288 of 2290 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/sq/ --- src/i18n/strings/sq.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sq.json b/src/i18n/strings/sq.json index 34507b1e3d..2656744f93 100644 --- a/src/i18n/strings/sq.json +++ b/src/i18n/strings/sq.json @@ -2524,5 +2524,7 @@ "Hey you. You're the best!": "Hej, ju. S’u ka kush shokun!", "Message layout": "Skemë mesazhesh", "Compact": "Kompakte", - "Modern": "Moderne" + "Modern": "Moderne", + "Use a system font": "Përdor një palë shkronja sistemi", + "System font name": "Emër shkronjash sistemi" } From a915da650a21fa909eb773a27113bb14809c27e0 Mon Sep 17 00:00:00 2001 From: XoseM Date: Tue, 23 Jun 2020 14:57:27 +0000 Subject: [PATCH 062/294] Translated using Weblate (Galician) Currently translated at 84.0% (1924 of 2290 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/gl/ --- src/i18n/strings/gl.json | 41 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index 9bd477f696..2e5678f266 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -1962,5 +1962,44 @@ "View Servers in Room": "Ver Servidores na Sala", "Verification Requests": "Solicitudes de Verificación", "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "Verifica esta usuaria para marcala como confiable. Ao confiar nas usuarias proporcionache tranquilidade extra cando usas cifrado de extremo-a-extremo.", - "Verifying this user will mark their session as trusted, and also mark your session as trusted to them.": "Ao verificar esta usuaria marcarás a súa sesión como confiable, e tamén marcará a túa sesión como confiable para elas." + "Verifying this user will mark their session as trusted, and also mark your session as trusted to them.": "Ao verificar esta usuaria marcarás a súa sesión como confiable, e tamén marcará a túa sesión como confiable para elas.", + "Enable IRC layout option in the appearance tab": "Activar opción de disposición IRC na pestana de aparencia", + "Use custom size": "Usar tamaño personalizado", + "Use a system font": "Usar tipo de letra do sistema", + "System font name": "Nome da letra do sistema", + "Hey you. You're the best!": "Ei ti. Es grande!", + "Message layout": "Disposición da mensaxe", + "Compact": "Compacta", + "Modern": "Moderna", + "Power level": "Poderío", + "Verify this device to mark it as trusted. Trusting this device gives you and other users extra peace of mind when using end-to-end encrypted messages.": "Verifica este dispositivo para marcalo como confiable. Confiando neste dispositivo permite que ti e outras usuarias estedes máis tranquilas ao utilizar mensaxes cifradas.", + "Verifying this device will mark it as trusted, and users who have verified with you will trust this device.": "Ao verificar este dispositivo marcaralo como confiable, e as usuarias que confiaron en ti tamén confiarán nel.", + "Waiting for partner to confirm...": "Agardando a que o compañeiro confirme...", + "Incoming Verification Request": "Solicitude entrante de verificación", + "Integrations are disabled": "As Integracións están desactivadas", + "Enable 'Manage Integrations' in Settings to do this.": "Activa 'Xestionar Integracións' nos Axustes para facer esto.", + "Integrations not allowed": "Non se permiten Integracións", + "Your Riot doesn't allow you to use an Integration Manager to do this. Please contact an admin.": "O teu Riot non permite que uses o Xestor de Integracións, contacta coa administración.", + "Confirm to continue": "Confirma para continuar", + "Click the button below to confirm your identity.": "Preme no botón inferior para confirmar a túa identidade.", + "Failed to invite the following users to chat: %(csvUsers)s": "Fallo ao convidar as seguintes usuarias a conversa: %(csvUsers)s", + "We couldn't create your DM. Please check the users you want to invite and try again.": "Non puidemos crear o teu MD. Comproba as usuarias que queres convidar e inténtao outra vez.", + "Something went wrong trying to invite the users.": "Algo fallou ao convidar as usuarias.", + "We couldn't invite those users. Please check the users you want to invite and try again.": "Non puidemos invitar esas usuarias. Comprobas que son correctas e intenta convidalas outra vez.", + "Failed to find the following users": "Non atopamos as seguintes usuarias", + "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "As seguintes usuarias poderían non existir ou non son válidas, e non se poden convidar: %(csvNames)s", + "Recent Conversations": "Conversas recentes", + "Suggestions": "Suxestións", + "Recently Direct Messaged": "Mensaxes Directas recentes", + "Start a conversation with someone using their name, username (like ) or email address.": "Inicia a conversa con alguén usando o seu nome, nome de usuaria (como ) ou enderezo de email.", + "Go": "Ir", + "Invite someone using their name, username (like ), email address or share this room.": "Convida alguén usando o seu nome, nome de usuaria (como ), enderezo de email ou comparte esta sala.", + "a new master key signature": "unha nova firma con chave mestra", + "a new cross-signing key signature": "unha nova firma con chave de sinatura-cruzada", + "a device cross-signing signature": "unha sinatura sinatura-cruzada de dispositivo", + "a key signature": "unha chave de sinatura", + "Riot encountered an error during upload of:": "Riot atopou un fallo ao subir:", + "Upload completed": "Subida completa", + "Cancelled signature upload": "Cancelada a subida da sinatura", + "Unable to upload": "Non foi posible a subida" } From f93d67fc65c9914286431b384020176c29b31e1d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 23 Jun 2020 16:49:39 -0600 Subject: [PATCH 063/294] Fix sticky room disappearing/jumping in search results Fixes https://github.com/vector-im/riot-web/issues/14124 Fixes https://github.com/vector-im/riot-web/issues/14154 (which was technically supposed to say that the sticky room when filtering was always last) This is all a bit complicated, but the theory is that we end up with a stable list even through filtering. There's some notes within, though I suspect it'll be difficult to understand :( --- src/stores/room-list/algorithms/Algorithm.ts | 47 +++++++++++++++++--- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/src/stores/room-list/algorithms/Algorithm.ts b/src/stores/room-list/algorithms/Algorithm.ts index 052c58bb83..9eb0d27748 100644 --- a/src/stores/room-list/algorithms/Algorithm.ts +++ b/src/stores/room-list/algorithms/Algorithm.ts @@ -170,12 +170,16 @@ export class Algorithm extends EventEmitter { // When we do have a room though, we expect to be able to find it const tag = this.roomIdsToTags[val.roomId][0]; if (!tag) throw new Error(`${val.roomId} does not belong to a tag and cannot be sticky`); - let position = this.cachedRooms[tag].indexOf(val); + + // We specifically do NOT use the ordered rooms set as it contains the sticky room, which + // means we'll be off by 1 when the user is switching rooms. This leads to visual jumping + // when the user is moving south in the list (not north, because of math). + let position = this.getOrderedRoomsWithoutSticky()[tag].indexOf(val); if (position < 0) throw new Error(`${val.roomId} does not appear to be known and cannot be sticky`); // 🐉 Here be dragons. // Before we can go through with lying to the underlying algorithm about a room - // we need to ensure that when we do we're ready for the innevitable sticky room + // we need to ensure that when we do we're ready for the inevitable sticky room // update we'll receive. To prepare for that, we first remove the sticky room and // recalculate the state ourselves so that when the underlying algorithm calls for // the same thing it no-ops. After we're done calling the algorithm, we'll issue @@ -208,6 +212,12 @@ export class Algorithm extends EventEmitter { position: position, tag: tag, }; + + // We update the filtered rooms just in case, as otherwise users will end up visiting + // a room while filtering and it'll disappear. We don't update the filter earlier in + // this function simply because we don't have to. + this.recalculateFilteredRoomsForTag(tag); + if (lastStickyRoom && lastStickyRoom.tag !== tag) this.recalculateFilteredRoomsForTag(tag); this.recalculateStickyRoom(); // Finally, trigger an update @@ -231,9 +241,7 @@ export class Algorithm extends EventEmitter { // We optimize our lookups by trying to reduce sample size as much as possible // to the rooms we know will be deduped by the Set. const rooms = this.cachedRooms[tagId].map(r => r); // cheap clone - if (this._stickyRoom && this._stickyRoom.tag === tagId && this._stickyRoom.room) { - rooms.push(this._stickyRoom.room); - } + this.tryInsertStickyRoomToFilterSet(rooms, tagId); let remainingRooms = rooms.map(r => r); let allowedRoomsInThisTag = []; let lastFilterPriority = orderedFilters[0].relativePriority; @@ -263,6 +271,7 @@ export class Algorithm extends EventEmitter { this.emit(LIST_UPDATED_EVENT); } + // TODO: Remove or use. protected addPossiblyFilteredRoomsToTag(tagId: TagID, added: Room[]): void { const filters = this.allowedByFilter.keys(); for (const room of added) { @@ -281,7 +290,8 @@ export class Algorithm extends EventEmitter { protected recalculateFilteredRoomsForTag(tagId: TagID): void { console.log(`Recalculating filtered rooms for ${tagId}`); delete this.filteredRooms[tagId]; - const rooms = this.cachedRooms[tagId]; + const rooms = this.cachedRooms[tagId].map(r => r); // cheap clone + this.tryInsertStickyRoomToFilterSet(rooms, tagId); const filteredRooms = rooms.filter(r => this.allowedRoomsByFilters.has(r)); if (filteredRooms.length > 0) { this.filteredRooms[tagId] = filteredRooms; @@ -289,6 +299,17 @@ export class Algorithm extends EventEmitter { console.log(`[DEBUG] ${filteredRooms.length}/${rooms.length} rooms filtered into ${tagId}`); } + protected tryInsertStickyRoomToFilterSet(rooms: Room[], tagId: TagID) { + if (!this._stickyRoom || !this._stickyRoom.room || this._stickyRoom.tag !== tagId) return; + + const position = this._stickyRoom.position; + if (position >= rooms.length) { + rooms.push(this._stickyRoom.room); + } else { + rooms.splice(position, 0, this._stickyRoom.room); + } + } + /** * Recalculate the sticky room position. If this is being called in relation to * a specific tag being updated, it should be given to this function to optimize @@ -377,6 +398,20 @@ export class Algorithm extends EventEmitter { return this.filteredRooms; } + /** + * This returns the same as getOrderedRooms(), but without the sticky room + * map as it causes issues for sticky room handling (see sticky room handling + * for more information). + * @returns {ITagMap} The cached list of rooms, ordered, + * for each tag. May be empty, but never null/undefined. + */ + private getOrderedRoomsWithoutSticky(): ITagMap { + if (!this.hasFilters) { + return this.cachedRooms; + } + return this.filteredRooms; + } + /** * Seeds the Algorithm with a set of rooms. The algorithm will discard all * previously known information and instead use these rooms instead. From 380aed4244a7b83183247ecf3a7f710ef28e9653 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 23 Jun 2020 20:59:26 -0600 Subject: [PATCH 064/294] Update profile information in User Menu Fixes https://github.com/vector-im/riot-web/issues/14158 (we needed an HTTP avatar URL) Fixes https://github.com/vector-im/riot-web/issues/14159 Fixes https://github.com/vector-im/riot-web/issues/14157 Also fixes an issue where it wasn't updating automatically when the user changed their profile info. This is all achieved through a new OwnProfileStore which does the heavy lifting, as we have to keep at least 2 components updated. --- res/css/structures/_LeftPanel2.scss | 9 ++ res/css/structures/_UserMenuButton.scss | 3 - src/components/structures/LeftPanel2.tsx | 41 ++++--- src/components/structures/UserMenuButton.tsx | 24 ++-- src/i18n/strings/en_EN.json | 2 +- src/stores/OwnProfileStore.ts | 122 +++++++++++++++++++ 6 files changed, 170 insertions(+), 31 deletions(-) create mode 100644 src/stores/OwnProfileStore.ts diff --git a/res/css/structures/_LeftPanel2.scss b/res/css/structures/_LeftPanel2.scss index 5cdefa0324..dd28a3107c 100644 --- a/res/css/structures/_LeftPanel2.scss +++ b/res/css/structures/_LeftPanel2.scss @@ -65,6 +65,10 @@ $tagPanelWidth: 70px; // only applies in this file, used for calculations .mx_LeftPanel2_userAvatarContainer { position: relative; // to make default avatars work margin-right: 8px; + + .mx_LeftPanel2_userAvatar { + border-radius: 32px; // should match avatar size + } } .mx_LeftPanel2_userName { @@ -72,6 +76,11 @@ $tagPanelWidth: 70px; // only applies in this file, used for calculations font-size: $font-15px; line-height: $font-20px; flex: 1; + + // Ellipsize any text overflow + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; } .mx_LeftPanel2_headerButtons { diff --git a/res/css/structures/_UserMenuButton.scss b/res/css/structures/_UserMenuButton.scss index 1fbbbb5fd8..3871fc22ef 100644 --- a/res/css/structures/_UserMenuButton.scss +++ b/res/css/structures/_UserMenuButton.scss @@ -35,9 +35,6 @@ limitations under the License. // Create another flexbox of columns to handle large user IDs display: flex; flex-direction: column; - - // fit the container - flex: 1; width: calc(100% - 40px); // 40px = 32px theme button + 8px margin to theme button * { diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index 378a24a70e..ec846bd177 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -15,6 +15,7 @@ limitations under the License. */ import * as React from "react"; +import { createRef } from "react"; import TagPanel from "./TagPanel"; import classNames from "classnames"; import dis from "../../dispatcher/dispatcher"; @@ -30,7 +31,9 @@ import RoomBreadcrumbs2 from "../views/rooms/RoomBreadcrumbs2"; import { BreadcrumbsStore } from "../../stores/BreadcrumbsStore"; import { UPDATE_EVENT } from "../../stores/AsyncStore"; import ResizeNotifier from "../../utils/ResizeNotifier"; -import { createRef } from "react"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { throttle } from 'lodash'; +import { OwnProfileStore } from "../../stores/OwnProfileStore"; /******************************************************************* * CAUTION * @@ -73,13 +76,32 @@ export default class LeftPanel2 extends React.Component { // We watch the middle panel because we don't actually get resized, the middle panel does. // We listen to the noisy channel to avoid choppy reaction times. this.props.resizeNotifier.on("middlePanelResizedNoisy", this.onResize); + + OwnProfileStore.instance.on(UPDATE_EVENT, this.onProfileUpdate); } public componentWillUnmount() { BreadcrumbsStore.instance.off(UPDATE_EVENT, this.onBreadcrumbsUpdate); this.props.resizeNotifier.off("middlePanelResizedNoisy", this.onResize); + OwnProfileStore.instance.off(UPDATE_EVENT, this.onProfileUpdate); } + // TSLint wants this to be a member, but we don't want that. + // tslint:disable-next-line + private onRoomStateUpdate = throttle((ev: MatrixEvent) => { + const myUserId = MatrixClientPeg.get().getUserId(); + if (ev.getType() === 'm.room.member' && ev.getSender() === myUserId && ev.getStateKey() === myUserId) { + // noinspection JSIgnoredPromiseFromCall + this.onProfileUpdate(); + } + }, 200, {trailing: true, leading: true}); + + private onProfileUpdate = async () => { + // the store triggered an update, so force a layout update. We don't + // have any state to store here for that to magically happen. + this.forceUpdate(); + }; + private onSearch = (term: string): void => { this.setState({searchFilter: term}); }; @@ -149,16 +171,7 @@ export default class LeftPanel2 extends React.Component { // TODO: Presence // TODO: Breadcrumbs toggle // TODO: Menu button - const avatarSize = 32; - // TODO: Don't do this profile lookup in render() - const client = MatrixClientPeg.get(); - let displayName = client.getUserId(); - let avatarUrl: string = null; - const myUser = client.getUser(client.getUserId()); - if (myUser) { - displayName = myUser.rawDisplayName; - avatarUrl = myUser.avatarUrl; - } + const avatarSize = 32; // should match border-radius of the avatar let breadcrumbs; if (this.state.showBreadcrumbs) { @@ -169,7 +182,7 @@ export default class LeftPanel2 extends React.Component { ); } - let name = {displayName}; + let name = {OwnProfileStore.instance.displayName}; let buttons = ( @@ -186,8 +199,8 @@ export default class LeftPanel2 extends React.Component { { this.state = { menuDisplayed: false, - user: MatrixClientPeg.get().getUser(MatrixClientPeg.get().getUserId()), isDarkTheme: this.isUserOnDarkTheme(), }; - } - private get displayName(): string { - if (MatrixClientPeg.get().isGuest()) { - return _t("Guest"); - } else if (this.state.user) { - return this.state.user.displayName; - } else { - return MatrixClientPeg.get().getUserId(); - } + OwnProfileStore.instance.on(UPDATE_EVENT, this.onProfileUpdate); } private get hasHomePage(): boolean { @@ -81,6 +72,7 @@ export default class UserMenuButton extends React.Component { public componentWillUnmount() { if (this.themeWatcherRef) SettingsStore.unwatchSetting(this.themeWatcherRef); if (this.dispatcherRef) defaultDispatcher.unregister(this.dispatcherRef); + OwnProfileStore.instance.off(UPDATE_EVENT, this.onProfileUpdate); } private isUserOnDarkTheme(): boolean { @@ -91,6 +83,12 @@ export default class UserMenuButton extends React.Component { return theme === "dark"; } + private onProfileUpdate = async () => { + // the store triggered an update, so force a layout update. We don't + // have any state to store here for that to magically happen. + this.forceUpdate(); + }; + private onThemeChanged = () => { this.setState({isDarkTheme: this.isUserOnDarkTheme()}); }; @@ -209,7 +207,7 @@ export default class UserMenuButton extends React.Component {
- {this.displayName} + {OwnProfileStore.instance.displayName} {MatrixClientPeg.get().getUserId()} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 197fa109e8..74e747726a 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -422,6 +422,7 @@ "Upgrade your Riot": "Upgrade your Riot", "A new version of Riot is available!": "A new version of Riot is available!", "You: %(message)s": "You: %(message)s", + "Guest": "Guest", "There was an error joining the room": "There was an error joining the room", "Sorry, your homeserver is too old to participate in this room.": "Sorry, your homeserver is too old to participate in this room.", "Please contact your homeserver administrator.": "Please contact your homeserver administrator.", @@ -2059,7 +2060,6 @@ "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", - "Guest": "Guest", "Your profile": "Your profile", "Uploading %(filename)s and %(count)s others|other": "Uploading %(filename)s and %(count)s others", "Uploading %(filename)s and %(count)s others|zero": "Uploading %(filename)s", diff --git a/src/stores/OwnProfileStore.ts b/src/stores/OwnProfileStore.ts new file mode 100644 index 0000000000..45d8829e30 --- /dev/null +++ b/src/stores/OwnProfileStore.ts @@ -0,0 +1,122 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { ActionPayload } from "../dispatcher/payloads"; +import { AsyncStoreWithClient } from "./AsyncStoreWithClient"; +import defaultDispatcher from "../dispatcher/dispatcher"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { User } from "matrix-js-sdk/src/models/user"; +import { throttle } from "lodash"; +import { MatrixClientPeg } from "../MatrixClientPeg"; +import { _t } from "../languageHandler"; + +interface IState { + displayName?: string; + avatarUrl?: string; +} + +export class OwnProfileStore extends AsyncStoreWithClient { + private static internalInstance = new OwnProfileStore(); + + private monitoredUser: User; + + private constructor() { + super(defaultDispatcher, {}); + } + + public static get instance(): OwnProfileStore { + return OwnProfileStore.internalInstance; + } + + /** + * Gets the display name for the user, or null if not present. + */ + public get displayName(): string { + if (!this.matrixClient) return this.state.displayName || null; + + if (this.matrixClient.isGuest()) { + return _t("Guest"); + } else if (this.state.displayName) { + return this.state.displayName; + } else { + return this.matrixClient.getUserId(); + } + } + + /** + * Gets the MXC URI of the user's avatar, or null if not present. + */ + public get avatarMxc(): string { + return this.state.avatarUrl || null; + } + + /** + * Gets the user's avatar as an HTTP URL of the given size. If the user's + * avatar is not present, this returns null. + * @param size The size of the avatar + * @returns The HTTP URL of the user's avatar + */ + public getHttpAvatarUrl(size: number): string { + if (!this.avatarMxc) return null; + return this.matrixClient.mxcUrlToHttp(this.avatarMxc, size, size); + } + + protected async onNotReady() { + if (this.monitoredUser) { + this.monitoredUser.removeListener("User.displayName", this.onProfileUpdate); + this.monitoredUser.removeListener("User.avatarUrl", this.onProfileUpdate); + } + if (this.matrixClient) { + this.matrixClient.removeListener("RoomState.events", this.onStateEvents); + } + await this.reset({}); + } + + protected async onReady() { + const myUserId = this.matrixClient.getUserId(); + this.monitoredUser = this.matrixClient.getUser(myUserId); + if (this.monitoredUser) { + this.monitoredUser.on("User.displayName", this.onProfileUpdate); + this.monitoredUser.on("User.avatarUrl", this.onProfileUpdate); + } + + // We also have to listen for membership events for ourselves as the above User events + // are fired only with presence, which matrix.org (and many others) has disabled. + this.matrixClient.on("RoomState.events", this.onStateEvents); + + await this.onProfileUpdate(); // trigger an initial update + } + + protected async onAction(payload: ActionPayload) { + // we don't actually do anything here + } + + private onProfileUpdate = async () => { + // We specifically do not use the User object we stored for profile info as it + // could easily be wrong (such as per-room instead of global profile). + const profileInfo = await this.matrixClient.getProfileInfo(this.matrixClient.getUserId()); + await this.updateState({displayName: profileInfo.displayname, avatarUrl: profileInfo.avatar_url}); + }; + + // TSLint wants this to be a member, but we don't want that. + // tslint:disable-next-line + private onStateEvents = throttle(async (ev: MatrixEvent) => { + const myUserId = MatrixClientPeg.get().getUserId(); + if (ev.getType() === 'm.room.member' && ev.getSender() === myUserId && ev.getStateKey() === myUserId) { + await this.onProfileUpdate(); + } + }, 200, {trailing: true, leading: true}); +} From 5c7e59b13200ab9dd652fca2f2e323d2dc982742 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 23 Jun 2020 21:17:39 -0600 Subject: [PATCH 065/294] Use theme-capable icons in the user menu They're still inconsistent weights, but at least they are the right color on non-light-theme clients. --- res/css/structures/_UserMenuButton.scss | 66 +++++++++++++++++++- src/components/structures/UserMenuButton.tsx | 16 ++--- 2 files changed, 73 insertions(+), 9 deletions(-) diff --git a/res/css/structures/_UserMenuButton.scss b/res/css/structures/_UserMenuButton.scss index 1fbbbb5fd8..768d643add 100644 --- a/res/css/structures/_UserMenuButton.scss +++ b/res/css/structures/_UserMenuButton.scss @@ -15,7 +15,26 @@ limitations under the License. */ .mx_UserMenuButton { - // No special styles on the button itself + > span { + width: 16px; + height: 16px; + position: relative; + display: block; + + &::before { + content: ''; + width: 16px; + height: 16px; + position: absolute; + top: 0; + left: 0; + mask-position: center; + mask-size: contain; + mask-repeat: no-repeat; + background: $primary-fg-color; + mask-image: url('$(res)/img/feather-customised/more-horizontal.svg'); + } + } } .mx_UserMenuButton_contextMenu { @@ -79,4 +98,49 @@ limitations under the License. justify-content: center; } } + + .mx_IconizedContextMenu_icon { + position: relative; + width: 16px; + height: 16px; + + &::before { + content: ''; + width: 16px; + height: 16px; + position: absolute; + mask-position: center; + mask-size: contain; + mask-repeat: no-repeat; + background: $primary-fg-color; + } + } + + .mx_UserMenuButton_iconHome::before { + mask-image: url('$(res)/img/feather-customised/home.svg'); + } + + .mx_UserMenuButton_iconBell::before { + mask-image: url('$(res)/img/feather-customised/notifications.svg'); + } + + .mx_UserMenuButton_iconLock::before { + mask-image: url('$(res)/img/feather-customised/lock.svg'); + } + + .mx_UserMenuButton_iconSettings::before { + mask-image: url('$(res)/img/feather-customised/settings.svg'); + } + + .mx_UserMenuButton_iconArchive::before { + mask-image: url('$(res)/img/feather-customised/archive.svg'); + } + + .mx_UserMenuButton_iconMessage::before { + mask-image: url('$(res)/img/feather-customised/message-circle.svg'); + } + + .mx_UserMenuButton_iconSignOut::before { + mask-image: url('$(res)/img/feather-customised/sign-out.svg'); + } } diff --git a/src/components/structures/UserMenuButton.tsx b/src/components/structures/UserMenuButton.tsx index 04b1b03368..f193a84648 100644 --- a/src/components/structures/UserMenuButton.tsx +++ b/src/components/structures/UserMenuButton.tsx @@ -190,7 +190,7 @@ export default class UserMenuButton extends React.Component { homeButton = (
  • - + {_t("Home")}
  • @@ -233,31 +233,31 @@ export default class UserMenuButton extends React.Component { {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")}
  • @@ -267,7 +267,7 @@ export default class UserMenuButton extends React.Component {
    • - + {_t("Sign out")}
    • @@ -287,7 +287,7 @@ export default class UserMenuButton extends React.Component { label={_t("Account settings")} isExpanded={this.state.menuDisplayed} > - ... + {/* masked image in CSS */} {contextMenu} From dd8109f77af74c65f46774ebd8bec6961297782b Mon Sep 17 00:00:00 2001 From: Slavi Pantaleev Date: Wed, 24 Jun 2020 07:44:19 +0000 Subject: [PATCH 066/294] Translated using Weblate (Bulgarian) Currently translated at 100.0% (2291 of 2291 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/bg/ --- src/i18n/strings/bg.json | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/bg.json b/src/i18n/strings/bg.json index 89edf90c8b..1b67bb7ced 100644 --- a/src/i18n/strings/bg.json +++ b/src/i18n/strings/bg.json @@ -2325,7 +2325,7 @@ "Verify this login": "Потвърди тази сесия", "Session verified": "Сесията беше потвърдена", "Changing your password will reset any end-to-end encryption keys on all of your sessions, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another session before resetting your password.": "Промяната на паролата ще нулира всички ключове за шифроване от-край-до-край по всички ваши сесии, правейки шифрованата история на чата нечетима. Настройте резервно копие на ключовете или експортирайте ключовете на стаите от друга сесия преди да промените паролата си.", - "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Администраторът на сървъра е изключиш шифроване от край-до-край по подразбиране за лични стаи и за директни съобщения.", + "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Администраторът на сървъра е изключил шифроване от край-до-край по подразбиране за лични стаи и за директни съобщения.", "Emoji picker": "Избор на емоджи", "People": "Хора", "Show %(n)s more": "Покажи още %(n)s", @@ -2446,5 +2446,27 @@ "A-Z": "Азбучен ред", "Unread rooms": "Непрочетени стаи", "Show %(count)s more|other": "Покажи още %(count)s", - "Show %(count)s more|one": "Покажи още %(count)s" + "Show %(count)s more|one": "Покажи още %(count)s", + "Light": "Светла", + "Dark": "Тъмна", + "Use the improved room list (will refresh to apply changes)": "Използвай подобрения списък със стаи (ще презареди за да се приложи промяната)", + "Enable IRC layout option in the appearance tab": "Включи опцията за IRC изглед в раздел Изглед", + "Use custom size": "Използвай собствен размер", + "Use a system font": "Използвай системния шрифт", + "System font name": "Име на системния шрифт", + "Hey you. You're the best!": "Хей, ти. Върхът си!", + "Message layout": "Изглед на съобщенията", + "Compact": "Компактен", + "Modern": "Модерен", + "Customise your appearance": "Настройте изгледа", + "Appearance Settings only affect this Riot session.": "Настройките на изгледа влияят само на тази Riot сесия.", + "The authenticity of this encrypted message can't be guaranteed on this device.": "Автентичността на това шифровано съобщение не може да бъде гарантирана на това устройство.", + "Always show first": "Винаги показвай първо", + "Show": "Покажи", + "Message preview": "Преглед на съобщението", + "List options": "Опции на списъка", + "Leave Room": "Напусни стаята", + "Room options": "Настройки на стаята", + "Use Recovery Key or Passphrase": "Използвай ключ за възстановяване или парола", + "Use Recovery Key": "Използвай ключ за възстановяване" } From bf34d37606771ae1969da6f7e092be10f872aaa4 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Wed, 24 Jun 2020 01:12:53 +0000 Subject: [PATCH 067/294] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2291 of 2291 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index b5bfcb4875..325f657af1 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2533,5 +2533,8 @@ "Hey you. You're the best!": "你是最棒的!", "Message layout": "訊息佈局", "Compact": "簡潔", - "Modern": "現代" + "Modern": "現代", + "Use a system font": "使用系統字型", + "System font name": "系統字型名稱", + "The authenticity of this encrypted message can't be guaranteed on this device.": "無法在此裝置上保證加密訊息的真實性。" } From 7cb48aac9bd0123b4ae730a88eb8ba269935ad94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Wed, 24 Jun 2020 07:01:40 +0000 Subject: [PATCH 068/294] Translated using Weblate (French) Currently translated at 100.0% (2291 of 2291 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/fr/ --- src/i18n/strings/fr.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index 1a89f1be6d..e6762eee32 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -2534,5 +2534,8 @@ "Hey you. You're the best!": "Eh vous. Vous êtes les meilleurs !", "Message layout": "Mise en page des messages", "Compact": "Compacte", - "Modern": "Moderne" + "Modern": "Moderne", + "Use a system font": "Utiliser une police du système", + "System font name": "Nom de la police du système", + "The authenticity of this encrypted message can't be guaranteed on this device.": "L’authenticité de ce message chiffré ne peut pas être garantie sur cet appareil." } From f1fc81be69de1a52531d2cf40fa19d53f8578d0b Mon Sep 17 00:00:00 2001 From: XoseM Date: Wed, 24 Jun 2020 07:16:08 +0000 Subject: [PATCH 069/294] Translated using Weblate (Galician) Currently translated at 84.8% (1943 of 2291 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/gl/ --- src/i18n/strings/gl.json | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index 2e5678f266..55b3f021bb 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -2001,5 +2001,24 @@ "Riot encountered an error during upload of:": "Riot atopou un fallo ao subir:", "Upload completed": "Subida completa", "Cancelled signature upload": "Cancelada a subida da sinatura", - "Unable to upload": "Non foi posible a subida" + "Unable to upload": "Non foi posible a subida", + "The authenticity of this encrypted message can't be guaranteed on this device.": "A autenticidade desta mensaxe cifrada non está garantida neste dispositivo.", + "Signature upload success": "Subeuse correctamente a sinatura", + "Signature upload failed": "Fallou a subida da sinatura", + "You've previously used Riot on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, Riot needs to resync your account.": "Anteriormente utilizaches Riot en %(host)s con carga preguiceira de membros. Nesta versión a carga preguiceira está desactivada. Como a caché local non é compatible entre as dúas configuracións, Riot precisa voltar a sincronizar a conta.", + "If the other version of Riot is still open in another tab, please close it as using Riot on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "Se a outra versión de Riot aínda está aberta noutra lapela, péchaa por favor, pois podería haber fallos ao estar as dúas sesións traballando simultáneamente.", + "Incompatible local cache": "Caché local incompatible", + "Clear cache and resync": "Baleirar caché e sincronizar", + "Riot now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "Riot utiliza agora entre 3 e 5 veces menos memoria, cargando só información sobre as usuarias cando é preciso. Agarda mentras se sincroniza co servidor!", + "Updating Riot": "Actualizando Riot", + "I don't want my encrypted messages": "Non quero as miñas mensaxes cifradas", + "Manually export keys": "Exportar manualmente as chaves", + "You'll lose access to your encrypted messages": "Perderás o acceso as túas mensaxes cifradas", + "Confirm by comparing the following with the User Settings in your other session:": "Corfirma comparando o seguinte cos Axustes de Usuaria na outra sesión:", + "Confirm this user's session by comparing the following with their User Settings:": "Confirma a sesión desta usuaria comparando o seguinte cos seus Axustes de Usuaria:", + "Session name": "Nome da sesión", + "Session key": "Chave da sesión", + "If they don't match, the security of your communication may be compromised.": "Se non concordan, a seguridade da comunicación podería estar comprometida.", + "Verify session": "Verificar sesión", + "Your homeserver doesn't seem to support this feature.": "O servidor non semella soportar esta característica." } From 354e31e7db9cefbba1bf95414cf713291d4525bb Mon Sep 17 00:00:00 2001 From: Szimszon Date: Wed, 24 Jun 2020 06:48:24 +0000 Subject: [PATCH 070/294] Translated using Weblate (Hungarian) Currently translated at 100.0% (2291 of 2291 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/hu/ --- src/i18n/strings/hu.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index ea830bc38e..05d68fd890 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -2516,5 +2516,14 @@ "Upgrade your Recovery Key": "A Visszaállítási kulcs fejlesztése", "Store your Recovery Key": "Visszaállítási kulcs tárolása", "Use the improved room list (in development - will refresh to apply changes)": "Használd a fejlesztett szoba listát (fejlesztés alatt - a változások a frissítés után aktiválódnak)", - "Use the improved room list (will refresh to apply changes)": "Használd a fejlesztett szoba listát (a változások életbe lépéséhez újra fog tölteni)" + "Use the improved room list (will refresh to apply changes)": "Használd a fejlesztett szoba listát (a változások életbe lépéséhez újra fog tölteni)", + "Enable IRC layout option in the appearance tab": "IRC kinézet lehetőségének megjelenítése a kinézet fülön", + "Use custom size": "Egyedi méret használata", + "Use a system font": "Rendszer betűtípus használata", + "System font name": "Rendszer betűtípus neve", + "Hey you. You're the best!": "Hé te! Te vagy a legjobb!", + "Message layout": "Üzenet kinézete", + "Compact": "Egyszerű", + "Modern": "Modern", + "The authenticity of this encrypted message can't be guaranteed on this device.": "A titkosított üzenetek valódiságát ezen az eszközön nem lehet garantálni." } From bced9346130d0f7c72dd7d8863ef80b7b2e24bc5 Mon Sep 17 00:00:00 2001 From: ziriSut Date: Wed, 24 Jun 2020 00:52:10 +0000 Subject: [PATCH 071/294] Translated using Weblate (Kabyle) Currently translated at 19.6% (449 of 2291 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/kab/ --- src/i18n/strings/kab.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/kab.json b/src/i18n/strings/kab.json index ad1b2ed516..7180e35b1f 100644 --- a/src/i18n/strings/kab.json +++ b/src/i18n/strings/kab.json @@ -450,5 +450,6 @@ "Send typing notifications": "Azen ilɣa yettuszemlen", "Show typing notifications": "Azen ilɣa yettuszemlen", "Room Colour": "Initen n texxamt", - "Show developer tools": "Sken ifecka n uneflay" + "Show developer tools": "Sken ifecka n uneflay", + "Whether or not you're using the Richtext mode of the Rich Text Editor": "Ma yella tseqdaceḍ askar n Richtext n umaẓrag n uḍris anesbaɣur" } From bf15e96a6a98764b2c8c12b92e82f4c332074171 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 24 Jun 2020 12:43:56 +0100 Subject: [PATCH 072/294] Make pasphrase screen look more like designs Although passphrase / passphrase confirm is still split between two screens because that's more work to change and probably is not a pivotal part of the UI that needs to change in step with everything else. --- .../CreateSecretStorageDialog.js | 59 ++++--------------- src/i18n/strings/en_EN.json | 8 +-- 2 files changed, 14 insertions(+), 53 deletions(-) diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index e81d017d7f..bc27efea46 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -85,8 +85,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent { canUploadKeysWithPasswordOnly: null, accountPassword: props.accountPassword || "", accountPasswordCorrect: null, - // status of the key backup toggle switch - useKeyBackup: true, passPhraseKeySelected: CREATESTORAGE_OPTION_KEY, }; @@ -172,12 +170,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent { this._recoveryKeyNode = n; } - _onUseKeyBackupChange = (enabled) => { - this.setState({ - useKeyBackup: enabled, - }); - } - _onChooseKeyPassphraseFormSubmit = async () => { if (this.state.passPhraseKeySelected === CREATESTORAGE_OPTION_KEY) { this._recoveryKey = @@ -291,22 +283,15 @@ export default class CreateSecretStorageDialog extends React.PureComponent { await cli.bootstrapSecretStorage({ authUploadDeviceSigningKeys: this._doBootstrapUIAuth, createSecretStorageKey: async () => this._recoveryKey, - setupNewKeyBackup: this.state.useKeyBackup, + setupNewKeyBackup: true, setupNewSecretStorage: true, }); - if (!this.state.useKeyBackup && this.state.backupInfo) { - // If the user is resetting their cross-signing keys and doesn't want - // key backup (but had it enabled before), delete the key backup as it's - // no longer valid. - console.log("Deleting invalid key backup (secrets have been reset; key backup not requested)"); - await cli.deleteKeyBackupVersion(this.state.backupInfo.version); - } } else { await cli.bootstrapSecretStorage({ authUploadDeviceSigningKeys: this._doBootstrapUIAuth, createSecretStorageKey: async () => this._recoveryKey, keyBackupInfo: this.state.backupInfo, - setupNewKeyBackup: !this.state.backupInfo && this.state.useKeyBackup, + setupNewKeyBackup: !this.state.backupInfo, getKeyBackupPassphrase: () => { // We may already have the backup key if we earlier went // through the restore backup path, so pass it along @@ -386,16 +371,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent { this.setState({phase: PHASE_CHOOSE_KEY_PASSPHRASE}); } - _onSkipPassPhraseClick = async () => { - this._recoveryKey = - await MatrixClientPeg.get().createRecoveryKeyFromPassphrase(); - this.setState({ - copied: false, - downloaded: false, - phase: PHASE_SHOWKEY, - }); - } - _onPassPhraseNextClick = async (e) => { e.preventDefault(); if (!this._passphraseField.current) return; // unmounting @@ -548,7 +523,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { hasCancel={false} primaryDisabled={this.state.canUploadKeysWithPasswordOnly && !this.state.accountPassword} > - @@ -556,12 +531,10 @@ export default class CreateSecretStorageDialog extends React.PureComponent { } _renderPhasePassPhrase() { - const LabelledToggleSwitch = sdk.getComponent('views.elements.LabelledToggleSwitch'); - return

      {_t( - "Set a recovery passphrase to secure encrypted information and recover it if you log out. " + - "This should be different to your account password:", + "Enter a security phrase only you know, as it’s used to safeguard your data. " + + "To be secure, you shouldn’t re-use your account password.", )}

      @@ -580,11 +553,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent { />
      - - + >{_t("Cancel")} - -
      - {_t("Advanced")} - - {_t("Set up with a recovery key")} - -
      ; } @@ -662,7 +623,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { disabled={this.state.passPhrase !== this.state.passPhraseConfirm} > @@ -772,9 +733,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent { case PHASE_MIGRATE: return _t('Upgrade your encryption'); case PHASE_PASSPHRASE: - return _t('Set up encryption'); + return _t('Set a Security Phrase'); case PHASE_PASSPHRASE_CONFIRM: - return _t('Confirm recovery passphrase'); + return _t('Confirm Security Phrase'); case PHASE_CONFIRM_SKIP: return _t('Are you sure?'); case PHASE_SHOWKEY: diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 0b7a261e45..7d67fc488a 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2181,11 +2181,9 @@ "Restore": "Restore", "You'll need to authenticate with the server to confirm the upgrade.": "You'll need to authenticate with the server to confirm the upgrade.", "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.", - "Set a recovery passphrase to secure encrypted information and recover it if you log out. This should be different to your account password:": "Set a recovery passphrase to secure encrypted information and recover it if you log out. This should be different to your account password:", + "Enter a security phrase only you know, as it’s used to safeguard your data. To be secure, you shouldn’t re-use your account password.": "Enter a security phrase only you know, as it’s used to safeguard your data. To be secure, you shouldn’t re-use your account password.", "Enter a recovery passphrase": "Enter a recovery passphrase", "Great! This recovery passphrase looks strong enough.": "Great! This recovery passphrase looks strong enough.", - "Back up encrypted message keys": "Back up encrypted message keys", - "Set up with a recovery key": "Set up with a recovery key", "That matches!": "That matches!", "Use a different passphrase?": "Use a different passphrase?", "That doesn't match.": "That doesn't match.", @@ -2202,12 +2200,14 @@ "You can also set up Secure Backup & manage your keys in Settings.": "You can also set up Secure Backup & manage your keys in Settings.", "Set up Secure backup": "Set up Secure backup", "Upgrade your encryption": "Upgrade your encryption", - "Confirm recovery passphrase": "Confirm recovery passphrase", + "Set a Security Phrase": "Set a Security Phrase", + "Confirm Security Phrase": "Confirm Security Phrase", "Save your Security Key": "Save your Security Key", "You're done!": "You're done!", "Unable to set up secret storage": "Unable to set up secret storage", "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.": "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.", "For maximum security, this should be different from your account password.": "For maximum security, this should be different from your account password.", + "Set up with a recovery key": "Set up with a recovery key", "Please enter your recovery passphrase a second time to confirm.": "Please enter your recovery passphrase a second time to confirm.", "Repeat your recovery passphrase...": "Repeat your recovery passphrase...", "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.": "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.", From b3fd1eda03ebe6ac252d842b22b8a8db48a1463c Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 24 Jun 2020 14:54:14 +0200 Subject: [PATCH 073/294] change the format of font faces to something closer to the css --- src/theme.js | 45 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/src/theme.js b/src/theme.js index ee9cb9c7db..bfee597da4 100644 --- a/src/theme.js +++ b/src/theme.js @@ -49,15 +49,46 @@ function clearCustomTheme() { } } +const allowedFontFaceProps = [ + "font-display", + "font-family", + "font-stretch", + "font-style", + "font-weight", + "font-variant", + "font-feature-settings", + "font-variation-settings", + "src", + "unicode-range" +]; + function generateCustomFontFaceCSS(faces) { - return Object.entries(faces).map(([fontName, face]) => { - const src = Object.entries(face.src).map(([format, url]) => { - return `url('${url}') format('${format}')`; + return faces.map(face => { + const src = face.src && face.src.map(srcElement => { + let format; + if (srcElement.format) { + format = `format("${srcElement.format}")`; + } + if (srcElement.url) { + return `url("${srcElement.url}") ${format}`; + } else if (srcElement.local) { + return `local("${srcElement.local}") ${format}`; + } + return ""; }).join(", "); - return `@font-face {` + - ` font-family: '${fontName}';` + - ` src: ${src};` + - `}`; + const props = Object.keys(face).filter(prop => allowedFontFaceProps.includes(prop)); + const body = props.map(prop => { + let value; + if (prop === "src") { + value = src; + } else if (prop === "font-family") { + value = `"${face[prop]}"`; + } else { + value = face[prop]; + } + return `${prop}: ${value}`; + }).join(";"); + return `@font-face {${body}}`; }).join("\n"); } From 183eb78fa8867858ff5d005be81ff79d9ba127d6 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 24 Jun 2020 14:58:41 +0200 Subject: [PATCH 074/294] fix lint --- src/theme.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/theme.js b/src/theme.js index bfee597da4..c79e466933 100644 --- a/src/theme.js +++ b/src/theme.js @@ -59,7 +59,7 @@ const allowedFontFaceProps = [ "font-feature-settings", "font-variation-settings", "src", - "unicode-range" + "unicode-range", ]; function generateCustomFontFaceCSS(faces) { From 256636ccf843ba79d8d000cbc4f72ab57668600d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 24 Jun 2020 07:04:19 -0600 Subject: [PATCH 075/294] Use display:block over absolute positioning --- res/css/structures/_UserMenuButton.scss | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/res/css/structures/_UserMenuButton.scss b/res/css/structures/_UserMenuButton.scss index 768d643add..fe5b6f29a2 100644 --- a/res/css/structures/_UserMenuButton.scss +++ b/res/css/structures/_UserMenuButton.scss @@ -100,15 +100,14 @@ limitations under the License. } .mx_IconizedContextMenu_icon { - position: relative; width: 16px; height: 16px; + display: block; &::before { content: ''; width: 16px; height: 16px; - position: absolute; mask-position: center; mask-size: contain; mask-repeat: no-repeat; From b01015f5c5a45ca0031d3a1f2d26417d55ca4e20 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 24 Jun 2020 14:30:12 +0100 Subject: [PATCH 076/294] Show cross-signing / secret storage reset button in more cases This exposes the cross-signing / secret storage reset button in more cases to hopefully give people a better chance of trying again in case something failed halfway through set up. In particular, any combination of keys existing now reveals the reset button. Fixes https://github.com/vector-im/riot-web/issues/13993 --- .../views/settings/CrossSigningPanel.js | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index 7eb239cbca..aa512d4365 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -154,13 +154,6 @@ export default class CrossSigningPanel extends React.PureComponent { errorSection =
      {error.toString()}
      ; } - // Whether the various keys exist on your account (but not necessarily - // on this device). - const enabledForAccount = ( - crossSigningPrivateKeysInStorage && - secretStorageKeyInAccount - ); - let summarisedStatus; if (homeserverSupportsCrossSigning === undefined) { const InlineSpinner = sdk.getComponent('views.elements.InlineSpinner'); @@ -184,8 +177,19 @@ export default class CrossSigningPanel extends React.PureComponent { )}

      ; } + const keysExistAnywhere = ( + secretStorageKeyInAccount || + crossSigningPrivateKeysInStorage || + crossSigningPublicKeysOnDevice + ); + const keysExistEverywhere = ( + secretStorageKeyInAccount && + crossSigningPrivateKeysInStorage && + crossSigningPublicKeysOnDevice + ); + let resetButton; - if (enabledForAccount) { + if (keysExistAnywhere) { resetButton = (
      @@ -197,10 +201,7 @@ export default class CrossSigningPanel extends React.PureComponent { // TODO: determine how better to expose this to users in addition to prompts at login/toast let bootstrapButton; - if ( - (!enabledForAccount || !crossSigningPublicKeysOnDevice) && - homeserverSupportsCrossSigning - ) { + if (!keysExistEverywhere && homeserverSupportsCrossSigning) { bootstrapButton = (
      From a23b784e005bd8098edee6dca76af368d69b6bd1 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 24 Jun 2020 15:21:09 +0100 Subject: [PATCH 077/294] Enable continue button if a passphrase has been set --- .../views/dialogs/secretstorage/CreateSecretStorageDialog.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index bc27efea46..14f5764927 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -78,6 +78,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { passPhraseConfirm: '', copied: false, downloaded: false, + setPassphrase: false, backupInfo: null, backupSigStatus: null, // does the server offer a UI auth flow with just m.login.password @@ -177,6 +178,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { this.setState({ copied: false, downloaded: false, + setPassphrase: false, phase: PHASE_SHOWKEY, }); } else { @@ -395,6 +397,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { this.setState({ copied: false, downloaded: false, + setPassphrase: true, phase: PHASE_SHOWKEY, }); } @@ -634,7 +637,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { let continueButton; if (this.state.phase === PHASE_SHOWKEY) { continueButton = ; From 0638b94cc24333814542c4dae898ccfe504ba19a Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Wed, 24 Jun 2020 16:06:50 +0100 Subject: [PATCH 078/294] Move compact checkbox --- .../tabs/user/_AppearanceUserSettingsTab.scss | 8 +++++++- src/components/views/elements/SettingsFlag.tsx | 14 ++++++++++++-- .../tabs/user/AppearanceUserSettingsTab.tsx | 10 ++++++++-- src/i18n/strings/en_EN.json | 2 +- src/settings/Settings.js | 2 +- 5 files changed, 29 insertions(+), 7 deletions(-) diff --git a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss index b24f548d60..d724b164e5 100644 --- a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss @@ -209,9 +209,15 @@ limitations under the License. } .mx_AppearanceUserSettingsTab_Advanced { + color: $primary-fg-color; + + > * { + margin-bottom: 16px; + } + .mx_AppearanceUserSettingsTab_AdvancedToggle { color: $accent-color; - margin-bottom: 16px; + cursor: pointer; } .mx_AppearanceUserSettingsTab_systemFont { diff --git a/src/components/views/elements/SettingsFlag.tsx b/src/components/views/elements/SettingsFlag.tsx index 9bdd04d803..4f41db51e2 100644 --- a/src/components/views/elements/SettingsFlag.tsx +++ b/src/components/views/elements/SettingsFlag.tsx @@ -30,6 +30,7 @@ interface IProps { isExplicit?: boolean; // XXX: once design replaces all toggles make this the default useCheckbox?: boolean; + disabled?: boolean; onChange?(checked: boolean): void; } @@ -78,14 +79,23 @@ export default class SettingsFlag extends React.Component { else label = _t(label); if (this.props.useCheckbox) { - return + return {label} ; } else { return (
      {label} - +
      ); } diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx index e935663bbe..9846be18d3 100644 --- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx @@ -391,7 +391,13 @@ export default class AppearanceUserSettingsTab extends React.Component + advanced = <> + -
      ; + ; } return
      {toggle} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 74e747726a..4970a650db 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -441,7 +441,7 @@ "Font size": "Font size", "Use custom size": "Use custom size", "Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing", - "Use compact timeline layout": "Use compact timeline layout", + "Use a more compact ‘Modern’ layout": "Use a more compact ‘Modern’ layout", "Show a placeholder for removed messages": "Show a placeholder for removed messages", "Show join/leave messages (invites/kicks/bans unaffected)": "Show join/leave messages (invites/kicks/bans unaffected)", "Show avatar changes": "Show avatar changes", diff --git a/src/settings/Settings.js b/src/settings/Settings.js index eb882b2d18..6c26967b1e 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -197,7 +197,7 @@ export const SETTINGS = { }, "useCompactLayout": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, - displayName: _td('Use compact timeline layout'), + displayName: _td('Use a more compact ‘Modern’ layout'), default: false, }, "showRedactions": { From 966837232c7b969e5cccabcb9d6e056de840013f Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 24 Jun 2020 16:12:46 +0100 Subject: [PATCH 079/294] Add header icons & justification --- res/css/_common.scss | 2 +- .../_CreateSecretStorageDialog.scss | 4 ++++ .../CreateSecretStorageDialog.js | 20 +++++++++++++++---- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/res/css/_common.scss b/res/css/_common.scss index e83c6aaeda..5f5a6d6999 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -319,7 +319,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { } .mx_Dialog_titleImage { - vertical-align: middle; + vertical-align: sub; width: 25px; height: 25px; margin-left: -2px; diff --git a/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss b/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss index 0d06d503b0..c591973c94 100644 --- a/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss +++ b/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss @@ -48,6 +48,10 @@ limitations under the License. margin-bottom: 1em; } +.mx_CreateSecretStorageDialog_centeredTitle, .mx_CreateSecretStorageDialog_centeredBody { + text-align: center; +} + .mx_CreateSecretStorageDialog_primaryContainer { /* FIXME: plinth colour in new theme(s). background-color: $accent-color; */ padding-top: 20px; diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index 14f5764927..936043b770 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -437,7 +437,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { _renderPhaseChooseKeyPassphrase() { return
      -

      {_t( +

      {_t( "Safeguard against losing access to encrypted messages & data by " + "backing up encryption keys on your server.", )}

      @@ -802,15 +802,27 @@ export default class CreateSecretStorageDialog extends React.PureComponent { } } - let headerImage; - if (this._titleForPhase(this.state.phase)) { - headerImage = require("../../../../../res/img/e2e/normal.svg"); + let headerImage = null; + switch (this.state.phase) { + case PHASE_PASSPHRASE: + case PHASE_PASSPHRASE_CONFIRM: + headerImage = require("../../../../../res/img/feather-customised/secure-phrase.svg"); + break; + case PHASE_SHOWKEY: + headerImage = require("../../../../../res/img/feather-customised/secure-backup.svg"); + break; + } + + let titleClass = null; + if (this.state.phase === PHASE_CHOOSE_KEY_PASSPHRASE) { + titleClass = 'mx_CreateSecretStorageDialog_centeredTitle'; } return ( Date: Wed, 24 Jun 2020 16:55:35 +0100 Subject: [PATCH 080/294] Remove the "You're done" screen --- .../CreateSecretStorageDialog.js | 23 +------------------ 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index 936043b770..b4b99f2205 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -39,7 +39,6 @@ const PHASE_PASSPHRASE = 4; const PHASE_PASSPHRASE_CONFIRM = 5; const PHASE_SHOWKEY = 6; const PHASE_STORING = 8; -const PHASE_DONE = 9; const PHASE_CONFIRM_SKIP = 10; const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc. @@ -305,9 +304,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { }, }); } - this.setState({ - phase: PHASE_DONE, - }); + this.props.onFinished(true); } catch (e) { if (this.state.canUploadKeysWithPasswordOnly && e.httpStatus === 401 && e.data.flows) { this.setState({ @@ -699,19 +696,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
      ; } - _renderPhaseDone() { - return
      -

      {_t( - "You can now verify your other devices, " + - "and other users to keep your chats safe.", - )}

      - -
      ; - } - _renderPhaseSkipConfirm() { return

      {_t( @@ -745,8 +729,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent { return _t('Save your Security Key'); case PHASE_STORING: return _t('Setting up keys'); - case PHASE_DONE: - return _t("You're done!"); default: return ''; } @@ -793,9 +775,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent { case PHASE_STORING: content = this._renderBusyPhase(); break; - case PHASE_DONE: - content = this._renderPhaseDone(); - break; case PHASE_CONFIRM_SKIP: content = this._renderPhaseSkipConfirm(); break; From 1aef7cdfeab8e0a59d82072dbda0f7098c5d9ab7 Mon Sep 17 00:00:00 2001 From: XoseM Date: Wed, 24 Jun 2020 14:42:49 +0000 Subject: [PATCH 081/294] Translated using Weblate (Galician) Currently translated at 91.4% (2095 of 2291 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/gl/ --- src/i18n/strings/gl.json | 154 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 153 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index 55b3f021bb..2a4bf15b89 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -2020,5 +2020,157 @@ "Session key": "Chave da sesión", "If they don't match, the security of your communication may be compromised.": "Se non concordan, a seguridade da comunicación podería estar comprometida.", "Verify session": "Verificar sesión", - "Your homeserver doesn't seem to support this feature.": "O servidor non semella soportar esta característica." + "Your homeserver doesn't seem to support this feature.": "O servidor non semella soportar esta característica.", + "Guest": "Convidada", + "Message edits": "Edicións da mensaxe", + "Your account is not secure": "A túa conta non é segura", + "Your password": "O teu contrasinal", + "This session, or the other session": "Esta sesión, ou a outra sesión", + "The internet connection either session is using": "A conexión a internet que está a usar cada sesión", + "We recommend you change your password and recovery key in Settings immediately": "Recomendámosche cambiar inmediatamente o contrasinal e chave de recuperación nos Axustes", + "New session": "Nova sesión", + "Use this session to verify your new one, granting it access to encrypted messages:": "Usa esta seseión para verificar a nova, dándolle acceso ás mensaxes cifradas:", + "This wasn't me": "Non fun eu", + "If you run into any bugs or have feedback you'd like to share, please let us know on GitHub.": "Se atopas fallos ou queres compartir a túa experiencia, compárteos con nós en GitHub.", + "Report bugs & give feedback": "Informe de fallos & opinión", + "Please fill why you're reporting.": "Escribe a razón do informe.", + "Report Content to Your Homeserver Administrator": "Denuncia sobre contido á Administración do teu servidor", + "Reporting this message will send its unique 'event ID' to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.": "Ao denunciar esta mensaxe vasnos enviar o seu 'event ID' único á administración do servidor. Se as mensaxes da sala están cifradas, a administración do servidor non poderá ler o texto da mensaxe ou ver imaxes ou ficheiros.", + "Send report": "Enviar denuncia", + "Failed to upgrade room": "Fallou a actualización da sala", + "The room upgrade could not be completed": "A actualización da sala non se completou", + "Upgrade this room to version %(version)s": "Actualiza esta sala á versión %(version)s", + "Upgrade Room Version": "Actualiza a Versión da Sala", + "Upgrading this room requires closing down the current instance of the room and creating a new room in its place. To give room members the best possible experience, we will:": "Para actualizar a sala debes pechar a instancia actual da sala e crear unha nova sala no seu lugar. Para proporcionar a mellor experiencia de usuaria, imos:", + "Create a new room with the same name, description and avatar": "Crear unha nova sala co mesmo nome, descrición e avatar", + "Update any local room aliases to point to the new room": "Actualizar calquera alias local da sala para que apunte á nova sala", + "Stop users from speaking in the old version of the room, and post a message advising users to move to the new room": "Evitar que as usuarias conversen na sala antiga e publicar unha mensaxe avisando ás usuarias para que veñan á nova sala", + "Put a link back to the old room at the start of the new room so people can see old messages": "Poñer unha ligazón na nova sala cara a antiga para que as persoas poidan ver as mensaxes antigas", + "Automatically invite users": "Convidar automáticamente ás usuarias", + "Upgrade private room": "Actualizar sala privada", + "Upgrade public room": "Actualizar sala pública", + "Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.": "A actualización da sala é unha acción avanzada e recomendada cando unha sala se volta inestable debido aos fallos, características obsoletas e vulnerabilidades da seguridade.", + "This usually only affects how the room is processed on the server. If you're having problems with your Riot, please report a bug.": "Esto normalmente só afecta ao xeito en que a sala se procesa no servidor. Se tes problemas con Riot, informa do problema.", + "You'll upgrade this room from to .": "Vas actualizar a sala da versión á .", + "A username can only contain lower case letters, numbers and '=_-./'": "Un nome de usuaria só pode ter minúsculas, números e '=_-./'", + "Checking...": "Comprobando...", + "Missing session data": "Faltan datos da sesión", + "Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.": "Faltan algúns datos da sesión, incluíndo chaves de mensaxes cifradas. Desconecta e volve a conectar para arranxalo, restaurando as chaves desde a copia.", + "Your browser likely removed this data when running low on disk space.": "O navegador probablemente eliminou estos datos ao quedar con pouco espazo de disco.", + "Integration Manager": "Xestor de Integracións", + "Find others by phone or email": "Atopa a outras por teléfono ou email", + "Be found by phone or email": "Permite ser atopada polo email ou teléfono", + "Use bots, bridges, widgets and sticker packs": "Usa bots, pontes, widgets e paquetes de adhesivos", + "Terms of Service": "Termos do Servizo", + "To continue you need to accept the terms of this service.": "Para continuar tes que aceptar os termos deste servizo.", + "Service": "Servizo", + "Summary": "Resumo", + "Document": "Documento", + "Next": "Seguinte", + "Upload files (%(current)s of %(total)s)": "Subir ficheiros (%(current)s de %(total)s)", + "Upload files": "Subir ficheiros", + "Upload all": "Subir todo", + "This file is too large to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.": "Este ficheiro é demasiado grande para subilo. O límite é %(limit)s mais o ficheiro é %(sizeOfThisFile)s.", + "These files are too large to upload. The file size limit is %(limit)s.": "Estes ficheiros son demasiado grandes para subilos. O límite é %(limit)s.", + "Some files are too large to be uploaded. The file size limit is %(limit)s.": "Algúns ficheiros son demasiado grandes para subilos. O límite é %(limit)s.", + "Upload %(count)s other files|other": "Subir outros %(count)s ficheiros", + "Upload %(count)s other files|one": "Subir %(count)s ficheiro máis", + "Cancel All": "Cancelar todo", + "Upload Error": "Fallo ao subir", + "Verify other session": "Verificar outra sesión", + "Verification Request": "Solicitude de Verificación", + "A widget would like to verify your identity": "Un widget quere verificar a túa indentidade", + "A widget located at %(widgetUrl)s would like to verify your identity. By allowing this, the widget will be able to verify your user ID, but not perform actions as you.": "Un widget localizado en %(widgetUrl)s quere verificar a túa identidade. Se o permites, o widget poderá verificar o teu ID de usuaria, pero non realizar accións por ti.", + "Remember my selection for this widget": "Lembrar a miña decisión para este widget", + "Deny": "Denegar", + "Enter recovery passphrase": "Escribe a frase de paso de recuperación", + "Unable to access secret storage. Please verify that you entered the correct recovery passphrase.": "Non se pode acceder ao almacenaxe segredo. Verifica que escribiches a frase de paso correta.", + "Warning: You should only do this on a trusted computer.": "Aviso: Só deberías facer esto nunha computadora de confianza.", + "Access your secure message history and your cross-signing identity for verifying other sessions by entering your recovery passphrase.": "Accede ó teu historial seguro de mensaxes e á túa identidade de sinatura-cruzada para verificar outras sesión escribindo a frase de paso de recuperación.", + "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options.": "Se esqueceches a túa frase de paso de recuperación podes usar a chave de recuperación ou establecer novas opcións de recuperación.", + "Enter recovery key": "Escribe a chave de recuperación", + "Unable to access secret storage. Please verify that you entered the correct recovery key.": "Non se accedeu ó almacenaxe segredo. Verifica que escribiches a chave de recuperación correcta.", + "This looks like a valid recovery key!": "Semella unha chave de recuperación válida!", + "Not a valid recovery key": "Non é unha chave de recuperación válida", + "Access your secure message history and your cross-signing identity for verifying other sessions by entering your recovery key.": "Accede ó teu historial de mensaxes seguras e á identidade de sinatura-cruzada para verificar outras sesión escribindo a achave de recuperación.", + "If you've forgotten your recovery key you can .": "Se esqueceches a chave de recuperación podes .", + "Restoring keys from backup": "Restablecendo chaves desde a copia", + "Fetching keys from server...": "Obtendo chaves desde o servidor...", + "%(completed)s of %(total)s keys restored": "%(completed)s de %(total)s chaves restablecidas", + "Unable to load backup status": "Non cargou o estado da copia", + "Recovery key mismatch": "A chave de recuperación non concorda", + "Backup could not be decrypted with this recovery key: please verify that you entered the correct recovery key.": "A copia non se puido descifrar con esta chave de recuperación: comproba que introduciches a chave de recuperación correcta.", + "Incorrect recovery passphrase": "Frase da paso de recuperación incorrecta", + "Backup could not be decrypted with this recovery passphrase: please verify that you entered the correct recovery passphrase.": "A copia non se descifrou con esta frase de paso: comproba que escribiches a frase de paso correcta.", + "Unable to restore backup": "Non se restableceu a copia", + "No backup found!": "Non se atopou copia!", + "Keys restored": "Chaves restablecidas", + "Failed to decrypt %(failedCount)s sessions!": "Fallo ao descifrar %(failedCount)s sesións!", + "Successfully restored %(sessionCount)s keys": "Restablecidas correctamente %(sessionCount)s chaves", + "Warning: you should only set up key backup from a trusted computer.": "Aviso: só deberías realizar a copia de apoio desde un ordenador de confianza.", + "Access your secure message history and set up secure messaging by entering your recovery passphrase.": "Accede ó historial de mensaxes seguras escribindo a frase de paso de recuperación.", + "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options": "Se esqueceches a frase de paso de recuperación pode usar a chave de recuperación ou establecer novas opcións de recuperación", + "Warning: You should only set up key backup from a trusted computer.": "Aviso: só deberías configurar a copia das chaves desde un ordenador de confianza.", + "Access your secure message history and set up secure messaging by entering your recovery key.": "Accede ó teu historial de mensaxes seguras e configura a comunicación segura escribindo a chave de recuperación.", + "If you've forgotten your recovery key you can ": "Se esqueceches a chave de recuperación podes ", + "Address (optional)": "Enderezo (optativo)", + "Resend edit": "Editar reenvío", + "Resend %(unsentCount)s reaction(s)": "Reenviar %(unsentCount)s reacción(s)", + "Resend removal": "Reenviar retirada", + "Share Permalink": "Comparte ligazón permanente", + "Report Content": "Denunciar contido", + "Notification settings": "Axustes de notificacións", + "Clear status": "Baleirar estado", + "Update status": "Actualizar estado", + "Set status": "Establecer estado", + "Set a new status...": "Establecer novo estado...", + "Hide": "Agochar", + "Reload": "Recargar", + "Take picture": "Tomar foto", + "Remove for everyone": "Eliminar para todas", + "Remove for me": "Eliminar para min", + "User Status": "Estado da usuaria", + "This homeserver would like to make sure you are not a robot.": "Este servidor quere asegurarse de que non es un robot.", + "Country Dropdown": "Despregable de países", + "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use this app with an existing Matrix account on a different homeserver.": "Podes usar as opcións dun servidor personalizado para conectarte a outros servidores Matrix indicando o URL do servidor. Así poderás usar esta app cunha conta Matrix dun servidor diferente.", + "Confirm your identity by entering your account password below.": "Confirma a túa identidade escribindo o contrasinal da conta embaixo.", + "Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.": "Falta a chave pública do captcha na configuración do servidor. Informa desto á administración do teu servidor.", + "Please review and accept all of the homeserver's policies": "Revisa e acepta todas as cláusulas do servidor", + "Please review and accept the policies of this homeserver:": "Revisa e acepta as cláusulas deste servidor:", + "Unable to validate homeserver/identity server": "Non se puido validar o servidor/servidor de identidade", + "Your Modular server": "O teu servidor Modular", + "Enter the location of your Modular homeserver. It may use your own domain name or be a subdomain of modular.im.": "Escribe a localización do teu servidor Modular. Podería utilizar o teu propio nome de dominio ou ser un subdominio de modular.im.", + "Server Name": "Nome do Servidor", + "Enter password": "Escribe contrasinal", + "Nice, strong password!": "Ben, bo contrasinal!", + "Password is allowed, but unsafe": "O contrasinal é admisible, pero inseguro", + "Keep going...": "Segue intentándoo...", + "The username field must not be blank.": "O campo de nome de usuaria non pode estar baleiro.", + "Username": "Nome de usuaria", + "Not sure of your password? Set a new one": "¿Non estás segura do contrasinal? Crea un novo", + "No identity server is configured so you cannot add an email address in order to reset your password in the future.": "Non hai un servidor de identidade configurado polo que non poderás engadir enderezos de email para poder restablecer o contrasinal no futuro.", + "Use an email address to recover your account": "Usa un enderezo de email para recuperar a túa conta", + "Enter email address (required on this homeserver)": "Escribe o enderzo de email (requerido neste servidor)", + "Doesn't look like a valid email address": "Non semella un enderezo válido", + "Passwords don't match": "Non concordan os contrasinais", + "Other users can invite you to rooms using your contact details": "Outras usuarias poden convidarte ás salas usando os teus detalles de contacto", + "Enter phone number (required on this homeserver)": "Escribe un número de teléfono (requerido neste servidor)", + "Doesn't look like a valid phone number": "Non semella un número de teléfono válido", + "Use lowercase letters, numbers, dashes and underscores only": "Usa só minúsculas, números, trazos e trazos baixos", + "Enter username": "Escribe nome de usuaria", + "Email (optional)": "Email (optativo)", + "Phone (optional)": "Teléfono (optativo)", + "Create your Matrix account on %(serverName)s": "Crea a conta Matrix en %(serverName)s", + "Create your Matrix account on ": "Crea a túa conta Matrix en ", + "Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.": "Establece un email para recuperación da conta. Usa un email ou teléfono de xeito optativo para que poidan atoparte os contactos.", + "Set an email for account recovery. Use email to optionally be discoverable by existing contacts.": "Establece un email para recuperación da conta. Optativamente usa un email para que poidan atoparte os contactos existentes.", + "Enter your custom homeserver URL What does this mean?": "Escribe o URL do servidor personalizado ¿Qué significa esto?", + "Homeserver URL": "URL do servidor", + "Enter your custom identity server URL What does this mean?": "Escribe o URL do servidor de identidade personalizado ¿Que significa esto?", + "Identity Server URL": "URL do servidor de identidade", + "Other servers": "Outros servidores", + "Free": "Gratuíto", + "Premium": "Premium", + "Premium hosting for organisations Learn more": "Hospedaxe Premium para organizacións Saber máis", + "Find other public servers or use a custom server": "Atopa outros servidores públicos ou usa un servidor personalizado" } From b4ab15f91f4d7f85444d5a1531bc5d8c82d56e0f Mon Sep 17 00:00:00 2001 From: random Date: Wed, 24 Jun 2020 13:32:40 +0000 Subject: [PATCH 082/294] Translated using Weblate (Italian) Currently translated at 99.9% (2290 of 2291 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/it/ --- src/i18n/strings/it.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index bf54e484bc..1e7f6d0957 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -2528,5 +2528,8 @@ "Hey you. You're the best!": "Ehi tu. Sei il migliore!", "Message layout": "Layout messaggio", "Compact": "Compatto", - "Modern": "Moderno" + "Modern": "Moderno", + "Use a system font": "Usa un carattere di sistema", + "System font name": "Nome carattere di sistema", + "The authenticity of this encrypted message can't be guaranteed on this device.": "L'autenticità di questo messaggio cifrato non può essere garantita su questo dispositivo." } From c047a76f1d60bb038f364278c5ea86335eca20a9 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 24 Jun 2020 10:36:43 -0600 Subject: [PATCH 083/294] Update the filtering for the right tag --- src/stores/room-list/algorithms/Algorithm.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stores/room-list/algorithms/Algorithm.ts b/src/stores/room-list/algorithms/Algorithm.ts index 9eb0d27748..5f7a7bd2ef 100644 --- a/src/stores/room-list/algorithms/Algorithm.ts +++ b/src/stores/room-list/algorithms/Algorithm.ts @@ -217,7 +217,7 @@ export class Algorithm extends EventEmitter { // a room while filtering and it'll disappear. We don't update the filter earlier in // this function simply because we don't have to. this.recalculateFilteredRoomsForTag(tag); - if (lastStickyRoom && lastStickyRoom.tag !== tag) this.recalculateFilteredRoomsForTag(tag); + if (lastStickyRoom && lastStickyRoom.tag !== tag) this.recalculateFilteredRoomsForTag(lastStickyRoom.tag); this.recalculateStickyRoom(); // Finally, trigger an update From 291914492fefae9162e84b904236baadb3afeb09 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 24 Jun 2020 19:20:43 -0600 Subject: [PATCH 084/294] Fix icons in the new user menu not showing up --- res/css/structures/_UserMenuButton.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/structures/_UserMenuButton.scss b/res/css/structures/_UserMenuButton.scss index 85c3f53aa1..c2bfe5b916 100644 --- a/res/css/structures/_UserMenuButton.scss +++ b/res/css/structures/_UserMenuButton.scss @@ -105,6 +105,7 @@ limitations under the License. content: ''; width: 16px; height: 16px; + display: block; mask-position: center; mask-size: contain; mask-repeat: no-repeat; From 752b2acc56007bd0ab70ac67eac509c029daece9 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 24 Jun 2020 12:23:09 -0600 Subject: [PATCH 085/294] Move MessagePreviewStore into the room list namespace --- src/components/views/rooms/RoomTile2.tsx | 2 +- src/stores/{ => room-list}/MessagePreviewStore.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) rename src/stores/{ => room-list}/MessagePreviewStore.ts (93%) diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index 18b4ee8185..63c9c1af23 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -34,7 +34,7 @@ import NotificationBadge, { import { _t } from "../../../languageHandler"; import { ContextMenu, ContextMenuButton } from "../../structures/ContextMenu"; import { DefaultTagID, TagID } from "../../../stores/room-list/models"; -import { MessagePreviewStore } from "../../../stores/MessagePreviewStore"; +import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore"; import RoomTileIcon from "./RoomTileIcon"; /******************************************************************* diff --git a/src/stores/MessagePreviewStore.ts b/src/stores/room-list/MessagePreviewStore.ts similarity index 93% rename from src/stores/MessagePreviewStore.ts rename to src/stores/room-list/MessagePreviewStore.ts index 9e225d1709..29fa45d882 100644 --- a/src/stores/MessagePreviewStore.ts +++ b/src/stores/room-list/MessagePreviewStore.ts @@ -15,13 +15,13 @@ limitations under the License. */ import { Room } from "matrix-js-sdk/src/models/room"; -import { ActionPayload } from "../dispatcher/payloads"; -import { AsyncStoreWithClient } from "./AsyncStoreWithClient"; -import defaultDispatcher from "../dispatcher/dispatcher"; -import { RoomListStoreTempProxy } from "./room-list/RoomListStoreTempProxy"; -import { textForEvent } from "../TextForEvent"; +import { ActionPayload } from "../../dispatcher/payloads"; +import { AsyncStoreWithClient } from "../AsyncStoreWithClient"; +import defaultDispatcher from "../../dispatcher/dispatcher"; +import { RoomListStoreTempProxy } from "./RoomListStoreTempProxy"; +import { textForEvent } from "../../TextForEvent"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; -import { _t } from "../languageHandler"; +import { _t } from "../../languageHandler"; const PREVIEWABLE_EVENTS = [ // This is the same list from RiotX From 37a415693f670e8193630c5913018c38e8d14e1c Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 24 Jun 2020 20:08:26 -0600 Subject: [PATCH 086/294] Allow the user to resize the new sublists to 1 tile For dogfooding https://github.com/vector-im/riot-web/issues/14137 To change the default: `localStorage.setItem("mx_dogfood_rl_defTiles", 4);` --- src/components/views/rooms/RoomSublist2.tsx | 2 +- src/stores/room-list/ListLayout.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index 6e3e90f805..5cbe10e160 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -117,7 +117,7 @@ export default class RoomSublist2 extends React.Component { }; private onShowLessClick = () => { - this.props.layout.visibleTiles = this.props.layout.minVisibleTiles; + this.props.layout.visibleTiles = this.props.layout.defaultVisibleTiles; this.forceUpdate(); // because the layout doesn't trigger a re-render }; diff --git a/src/stores/room-list/ListLayout.ts b/src/stores/room-list/ListLayout.ts index ebc7b95854..370777ef8b 100644 --- a/src/stores/room-list/ListLayout.ts +++ b/src/stores/room-list/ListLayout.ts @@ -67,6 +67,7 @@ export class ListLayout { } public get visibleTiles(): number { + if (this._n === 0) return this.defaultVisibleTiles; return Math.max(this._n, this.minVisibleTiles); } @@ -78,7 +79,13 @@ export class ListLayout { public get minVisibleTiles(): number { // the .65 comes from the CSS where the show more button is // mathematically 65% of a tile when floating. - return 4.65; + return 1.65; + } + + public get defaultVisibleTiles(): number { + // TODO: Remove dogfood flag + const val = Number(localStorage.getItem("mx_dogfood_rl_defTiles") || 4); + return val + 0.65; // see minVisibleTiles for where the .65 comes from } public calculateTilesToPixelsMin(maxTiles: number, n: number, possiblePadding: number): number { From 90ff4585d5979f354af2b118f52ca1e5f7400d53 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 24 Jun 2020 20:14:01 -0600 Subject: [PATCH 087/294] Remove extraneous debug from the new left panel --- src/components/structures/LeftPanel2.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index ec846bd177..27583f26ee 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -161,7 +161,6 @@ export default class LeftPanel2 extends React.Component { }; private onResize = () => { - console.log("Resize width"); if (!this.listContainerRef.current) return; // ignore: no headers to sticky this.handleStickyHeaders(this.listContainerRef.current); }; From dc099efb19d41360dc248e626b65592a9b06baf0 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 25 Jun 2020 08:43:35 +0100 Subject: [PATCH 088/294] make Notifier getSoundForRoom synchronous Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/Notifier.js | 4 ++-- .../settings/tabs/room/NotificationSettingsTab.js | 11 +++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Notifier.js b/src/Notifier.js index cd328ba565..b6690959d2 100644 --- a/src/Notifier.js +++ b/src/Notifier.js @@ -122,7 +122,7 @@ const Notifier = { } }, - getSoundForRoom: async function(roomId) { + getSoundForRoom: function(roomId) { // We do no caching here because the SDK caches setting // and the browser will cache the sound. const content = SettingsStore.getValue("notificationSound", roomId); @@ -151,7 +151,7 @@ const Notifier = { }, _playAudioNotification: async function(ev, room) { - const sound = await this.getSoundForRoom(room.roomId); + const sound = this.getSoundForRoom(room.roomId); console.log(`Got sound ${sound && sound.name || "default"} for ${room.roomId}`); try { diff --git a/src/components/views/settings/tabs/room/NotificationSettingsTab.js b/src/components/views/settings/tabs/room/NotificationSettingsTab.js index 96e6b3d354..c521e228e0 100644 --- a/src/components/views/settings/tabs/room/NotificationSettingsTab.js +++ b/src/components/views/settings/tabs/room/NotificationSettingsTab.js @@ -39,12 +39,11 @@ export default class NotificationsSettingsTab extends React.Component { // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs UNSAFE_componentWillMount() { // eslint-disable-line camelcase - Notifier.getSoundForRoom(this.props.roomId).then((soundData) => { - if (!soundData) { - return; - } - this.setState({currentSound: soundData.name || soundData.url}); - }); + const soundData = Notifier.getSoundForRoom(this.props.roomId); + if (!soundData) { + return; + } + this.setState({currentSound: soundData.name || soundData.url}); this._soundUpload = createRef(); } From dbe575d523b35cb3972e76e75e7ff681118df262 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 25 Jun 2020 08:44:24 +0100 Subject: [PATCH 089/294] Remove DEBUG Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/toasts/AnalyticsToast.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/toasts/AnalyticsToast.tsx b/src/toasts/AnalyticsToast.tsx index 7cd59222dd..b186a65d9d 100644 --- a/src/toasts/AnalyticsToast.tsx +++ b/src/toasts/AnalyticsToast.tsx @@ -24,14 +24,12 @@ import GenericToast from "../components/views/toasts/GenericToast"; import ToastStore from "../stores/ToastStore"; const onAccept = () => { - console.log("DEBUG onAccept AnalyticsToast"); dis.dispatch({ action: 'accept_cookies', }); }; const onReject = () => { - console.log("DEBUG onReject AnalyticsToast"); dis.dispatch({ action: "reject_cookies", }); From d4eebd5202387dd4df9bff6710222827c995c614 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 25 Jun 2020 08:58:54 +0100 Subject: [PATCH 090/294] Fix alwaysShowBadgeCounts settings defn Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/settings/Settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/Settings.js b/src/settings/Settings.js index eb882b2d18..5948a4ec9e 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -192,7 +192,7 @@ export const SETTINGS = { }, // TODO: Wire up appropriately to UI (FTUE notifications) "Notifications.alwaysShowBadgeCounts": { - supportedLevels: ['account'], + supportedLevels: LEVELS_ROOM_OR_ACCOUNT, default: false, }, "useCompactLayout": { From 8743af56ad8f9b71dc32f507fedede5b51b0d767 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 25 Jun 2020 09:00:13 +0100 Subject: [PATCH 091/294] Bring notification utils into this century to simplify ongoing Notifications work Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../{ContentRules.js => ContentRules.ts} | 57 ++++++--- ...ificationUtils.js => NotificationUtils.ts} | 26 ++-- ...eVectorState.js => PushRuleVectorState.ts} | 47 ++++---- ...{StandardActions.js => StandardActions.ts} | 2 - src/notifications/types.ts | 111 ++++++++++++++++++ 5 files changed, 189 insertions(+), 54 deletions(-) rename src/notifications/{ContentRules.js => ContentRules.ts} (69%) rename src/notifications/{NotificationUtils.js => NotificationUtils.ts} (80%) rename src/notifications/{PushRuleVectorState.js => PushRuleVectorState.ts} (69%) rename src/notifications/{StandardActions.js => StandardActions.ts} (98%) create mode 100644 src/notifications/types.ts diff --git a/src/notifications/ContentRules.js b/src/notifications/ContentRules.ts similarity index 69% rename from src/notifications/ContentRules.js rename to src/notifications/ContentRules.ts index 8c285220c7..a3ec017e37 100644 --- a/src/notifications/ContentRules.js +++ b/src/notifications/ContentRules.ts @@ -1,6 +1,6 @@ /* Copyright 2016 OpenMarket Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019, 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. @@ -15,9 +15,17 @@ See the License for the specific language governing permissions and limitations under the License. */ -'use strict'; +import {PushRuleVectorState, State} from "./PushRuleVectorState"; +import {IExtendedPushRule, IPushRuleSet, IRuleSets} from "./types"; -import {PushRuleVectorState} from "./PushRuleVectorState"; +export interface IContentRules { + vectorState: State; + rules: IExtendedPushRule[]; + externalRules: IExtendedPushRule[]; +} + +export const SCOPE = "global"; +export const KIND = "content"; export class ContentRules { /** @@ -31,7 +39,7 @@ export class ContentRules { * externalRules: a list of other keyword rules, with states other than * vectorState */ - static parseContentRules(rulesets) { + static parseContentRules(rulesets: IRuleSets): IContentRules { // first categorise the keyword rules in terms of their actions const contentRules = this._categoriseContentRules(rulesets); @@ -51,59 +59,72 @@ export class ContentRules { if (contentRules.loud.length) { return { - vectorState: PushRuleVectorState.LOUD, + vectorState: State.Loud, rules: contentRules.loud, - externalRules: [].concat(contentRules.loud_but_disabled, contentRules.on, contentRules.on_but_disabled, contentRules.other), + externalRules: [ + ...contentRules.loud_but_disabled, + ...contentRules.on, + ...contentRules.on_but_disabled, + ...contentRules.other, + ], }; } else if (contentRules.loud_but_disabled.length) { return { - vectorState: PushRuleVectorState.OFF, + vectorState: State.Off, rules: contentRules.loud_but_disabled, - externalRules: [].concat(contentRules.on, contentRules.on_but_disabled, contentRules.other), + externalRules: [...contentRules.on, ...contentRules.on_but_disabled, ...contentRules.other], }; } else if (contentRules.on.length) { return { - vectorState: PushRuleVectorState.ON, + vectorState: State.On, rules: contentRules.on, - externalRules: [].concat(contentRules.on_but_disabled, contentRules.other), + externalRules: [...contentRules.on_but_disabled, ...contentRules.other], }; } else if (contentRules.on_but_disabled.length) { return { - vectorState: PushRuleVectorState.OFF, + vectorState: State.Off, rules: contentRules.on_but_disabled, externalRules: contentRules.other, }; } else { return { - vectorState: PushRuleVectorState.ON, + vectorState: State.On, rules: [], externalRules: contentRules.other, }; } } - static _categoriseContentRules(rulesets) { - const contentRules = {on: [], on_but_disabled: [], loud: [], loud_but_disabled: [], other: []}; + static _categoriseContentRules(rulesets: IRuleSets) { + const contentRules: Record<"on"|"on_but_disabled"|"loud"|"loud_but_disabled"|"other", IExtendedPushRule[]> = { + on: [], + on_but_disabled: [], + loud: [], + loud_but_disabled: [], + other: [], + }; + for (const kind in rulesets.global) { for (let i = 0; i < Object.keys(rulesets.global[kind]).length; ++i) { const r = rulesets.global[kind][i]; // check it's not a default rule - if (r.rule_id[0] === '.' || kind !== 'content') { + if (r.rule_id[0] === '.' || kind !== "content") { continue; } - r.kind = kind; // is this needed? not sure + // this is needed as we are flattening an object of arrays into a single array + r.kind = kind; switch (PushRuleVectorState.contentRuleVectorStateKind(r)) { - case PushRuleVectorState.ON: + case State.On: if (r.enabled) { contentRules.on.push(r); } else { contentRules.on_but_disabled.push(r); } break; - case PushRuleVectorState.LOUD: + case State.Loud: if (r.enabled) { contentRules.loud.push(r); } else { diff --git a/src/notifications/NotificationUtils.js b/src/notifications/NotificationUtils.ts similarity index 80% rename from src/notifications/NotificationUtils.js rename to src/notifications/NotificationUtils.ts index bf393da060..e3b7f66447 100644 --- a/src/notifications/NotificationUtils.js +++ b/src/notifications/NotificationUtils.ts @@ -1,6 +1,6 @@ /* Copyright 2016 OpenMarket Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019, 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. @@ -15,7 +15,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -'use strict'; +import {Action, Actions} from "./types"; + +interface IEncodedActions { + notify: boolean; + sound?: string; + highlight?: boolean; +} export class NotificationUtils { // Encodes a dictionary of { @@ -24,12 +30,12 @@ export class NotificationUtils { // "highlight: true/false, // } // to a list of push actions. - static encodeActions(action) { + static encodeActions(action: IEncodedActions) { const notify = action.notify; const sound = action.sound; const highlight = action.highlight; if (notify) { - const actions = ["notify"]; + const actions: Action[] = [Actions.Notify]; if (sound) { actions.push({"set_tweak": "sound", "value": sound}); } @@ -40,7 +46,7 @@ export class NotificationUtils { } return actions; } else { - return ["dont_notify"]; + return [Actions.DontNotify]; } } @@ -50,18 +56,18 @@ export class NotificationUtils { // "highlight: true/false, // } // If the actions couldn't be decoded then returns null. - static decodeActions(actions) { + static decodeActions(actions: Action[]): IEncodedActions { let notify = false; let sound = null; let highlight = false; for (let i = 0; i < actions.length; ++i) { const action = actions[i]; - if (action === "notify") { + if (action === Actions.Notify) { notify = true; - } else if (action === "dont_notify") { + } else if (action === Actions.DontNotify) { notify = false; - } else if (typeof action === 'object') { + } else if (typeof action === "object") { if (action.set_tweak === "sound") { sound = action.value; } else if (action.set_tweak === "highlight") { @@ -81,7 +87,7 @@ export class NotificationUtils { highlight = true; } - const result = {notify: notify, highlight: highlight}; + const result: IEncodedActions = { notify, highlight }; if (sound !== null) { result.sound = sound; } diff --git a/src/notifications/PushRuleVectorState.js b/src/notifications/PushRuleVectorState.ts similarity index 69% rename from src/notifications/PushRuleVectorState.js rename to src/notifications/PushRuleVectorState.ts index 263226ce1c..d33426cfc4 100644 --- a/src/notifications/PushRuleVectorState.js +++ b/src/notifications/PushRuleVectorState.ts @@ -1,6 +1,6 @@ /* Copyright 2016 OpenMarket Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019, 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. @@ -15,43 +15,42 @@ See the License for the specific language governing permissions and limitations under the License. */ -'use strict'; - import {StandardActions} from "./StandardActions"; import {NotificationUtils} from "./NotificationUtils"; +import {IPushRule} from "./types"; + +export enum State { + /** The push rule is disabled */ + Off = "off", + /** The user will receive push notification for this rule */ + On = "on", + /** The user will receive push notification for this rule with sound and + highlight if this is legitimate */ + Loud = "loud", +} export class PushRuleVectorState { - // Backwards compatibility (things should probably be using .states instead) - static OFF = "off"; - static ON = "on"; - static LOUD = "loud"; + // Backwards compatibility (things should probably be using the enum above instead) + static OFF = State.Off; + static ON = State.On; + static LOUD = State.Loud; /** * Enum for state of a push rule as defined by the Vector UI. * @readonly * @enum {string} */ - static states = { - /** The push rule is disabled */ - OFF: PushRuleVectorState.OFF, - - /** The user will receive push notification for this rule */ - ON: PushRuleVectorState.ON, - - /** The user will receive push notification for this rule with sound and - highlight if this is legitimate */ - LOUD: PushRuleVectorState.LOUD, - }; + static states = State; /** * Convert a PushRuleVectorState to a list of actions * * @return [object] list of push-rule actions */ - static actionsFor(pushRuleVectorState) { - if (pushRuleVectorState === PushRuleVectorState.ON) { + static actionsFor(pushRuleVectorState: State) { + if (pushRuleVectorState === State.On) { return StandardActions.ACTION_NOTIFY; - } else if (pushRuleVectorState === PushRuleVectorState.LOUD) { + } else if (pushRuleVectorState === State.Loud) { return StandardActions.ACTION_HIGHLIGHT_DEFAULT_SOUND; } } @@ -63,7 +62,7 @@ export class PushRuleVectorState { * category or in PushRuleVectorState.LOUD, regardless of its enabled * state. Returns null if it does not match these categories. */ - static contentRuleVectorStateKind(rule) { + static contentRuleVectorStateKind(rule: IPushRule): State { const decoded = NotificationUtils.decodeActions(rule.actions); if (!decoded) { @@ -81,10 +80,10 @@ export class PushRuleVectorState { let stateKind = null; switch (tweaks) { case 0: - stateKind = PushRuleVectorState.ON; + stateKind = State.On; break; case 2: - stateKind = PushRuleVectorState.LOUD; + stateKind = State.Loud; break; } return stateKind; diff --git a/src/notifications/StandardActions.js b/src/notifications/StandardActions.ts similarity index 98% rename from src/notifications/StandardActions.js rename to src/notifications/StandardActions.ts index b54cea332a..c17010af9a 100644 --- a/src/notifications/StandardActions.js +++ b/src/notifications/StandardActions.ts @@ -15,8 +15,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -'use strict'; - import {NotificationUtils} from "./NotificationUtils"; const encodeActions = NotificationUtils.encodeActions; diff --git a/src/notifications/types.ts b/src/notifications/types.ts new file mode 100644 index 0000000000..9622193740 --- /dev/null +++ b/src/notifications/types.ts @@ -0,0 +1,111 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +export enum NotificationSetting { + AllMessages = "all_messages", // .m.rule.message = notify + DirectMessagesMentionsKeywords = "dm_mentions_keywords", // .m.rule.message = mark_unread. This is the new default. + MentionsKeywordsOnly = "mentions_keywords", // .m.rule.message = mark_unread; .m.rule.room_one_to_one = mark_unread + Never = "never", // .m.rule.master = enabled (dont_notify) +} + +export interface ISoundTweak { + set_tweak: "sound"; + value: string; +} +export interface IHighlightTweak { + set_tweak: "highlight"; + value?: boolean; +} + +export type Tweak = ISoundTweak | IHighlightTweak; + +export enum Actions { + Notify = "notify", + DontNotify = "dont_notify", // no-op + Coalesce = "coalesce", // unused + MarkUnread = "mark_unread", // new +} + +export type Action = Actions | Tweak; + +// Push rule kinds in descending priority order +export enum Kind { + Override = "override", + ContentSpecific = "content", + RoomSpecific = "room", + SenderSpecific = "sender", + Underride = "underride", +} + +export interface IEventMatchCondition { + kind: "event_match"; + key: string; + pattern: string; +} + +export interface IContainsDisplayNameCondition { + kind: "contains_display_name"; +} + +export interface IRoomMemberCountCondition { + kind: "room_member_count"; + is: string; +} + +export interface ISenderNotificationPermissionCondition { + kind: "sender_notification_permission"; + key: string; +} + +export type Condition = + IEventMatchCondition | + IContainsDisplayNameCondition | + IRoomMemberCountCondition | + ISenderNotificationPermissionCondition; + +export enum RuleIds { + MasterRule = ".m.rule.master", // The master rule (all notifications disabling) + MessageRule = ".m.rule.message", + EncryptedMessageRule = ".m.rule.encrypted", + RoomOneToOneRule = ".m.rule.room_one_to_one", + EncryptedRoomOneToOneRule = ".m.rule.room_one_to_one", +} + +export interface IPushRule { + enabled: boolean; + rule_id: RuleIds | string; + actions: Action[]; + default: boolean; + conditions?: Condition[]; // only applicable to `underride` and `override` rules + pattern?: string; // only applicable to `content` rules +} + +// push rule extended with kind, used by ContentRules and js-sdk's pushprocessor +export interface IExtendedPushRule extends IPushRule { + kind: Kind; +} + +export interface IPushRuleSet { + override: IPushRule[]; + content: IPushRule[]; + room: IPushRule[]; + sender: IPushRule[]; + underride: IPushRule[]; +} + +export interface IRuleSets { + global: IPushRuleSet; +} From 2b144a846a91db048f1d31d4102ed4ffc37b7787 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 25 Jun 2020 12:44:15 +0100 Subject: [PATCH 092/294] Apply some of the newer styling to passphrase / recovery key entry --- .../_AccessSecretStorageDialog.scss | 5 -- .../AccessSecretStorageDialog.js | 66 +++++-------------- src/i18n/strings/en_EN.json | 14 ++-- 3 files changed, 24 insertions(+), 61 deletions(-) diff --git a/res/css/views/dialogs/secretstorage/_AccessSecretStorageDialog.scss b/res/css/views/dialogs/secretstorage/_AccessSecretStorageDialog.scss index db11e91bdb..785eb85374 100644 --- a/res/css/views/dialogs/secretstorage/_AccessSecretStorageDialog.scss +++ b/res/css/views/dialogs/secretstorage/_AccessSecretStorageDialog.scss @@ -19,11 +19,6 @@ limitations under the License. height: 30px; } -.mx_AccessSecretStorageDialog_primaryContainer { - /* FIXME: plinth colour in new theme(s). background-color: $accent-color; */ - padding: 20px; -} - .mx_AccessSecretStorageDialog_passPhraseInput, .mx_AccessSecretStorageDialog_recoveryKeyInput { width: 300px; diff --git a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js index e2ceadfbb9..9d443b49ab 100644 --- a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js +++ b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js @@ -118,10 +118,12 @@ export default class AccessSecretStorageDialog extends React.PureComponent { let content; let title; + let headerImage; if (hasPassphrase && !this.state.forceRecoveryKey) { const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); - title = _t("Enter recovery passphrase"); + title = _t("Security Phrase"); + headerImage = require("../../../../../res/img/feather-customised/secure-phrase.svg"); let keyStatus; if (this.state.keyMatches === false) { @@ -137,12 +139,15 @@ export default class AccessSecretStorageDialog extends React.PureComponent { content =

      {_t( - "Warning: You should only do this on a trusted computer.", {}, - { b: sub => {sub} }, - )}

      -

      {_t( - "Access your secure message history and your cross-signing " + - "identity for verifying other sessions by entering your recovery passphrase.", + "Enter your Security Phrase or to continue.", {}, + { + button: s => + {s} + , + }, )}

      @@ -156,7 +161,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent { /> {keyStatus} - {_t( - "If you've forgotten your recovery passphrase you can "+ - "use your recovery key or " + - "set up new recovery options." - , {}, { - button1: s => - {s} - , - button2: s => - {s} - , - })}
      ; } else { - title = _t("Enter recovery key"); + title = _t("Security Key"); + headerImage = require("../../../../../res/img/feather-customised/secure-backup.svg"); const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); let keyStatus; if (this.state.recoveryKey.length === 0) { @@ -209,14 +196,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent { } content =
      -

      {_t( - "Warning: You should only do this on a trusted computer.", {}, - { b: sub => {sub} }, - )}

      -

      {_t( - "Access your secure message history and your cross-signing " + - "identity for verifying other sessions by entering your recovery key.", - )}

      +

      {_t("Use your Security Key to continue.")}

      {keyStatus} - {_t( - "If you've forgotten your recovery key you can "+ - "." - , {}, { - button: s => - {s} - , - })}
      ; } return ( diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 7d67fc488a..d551772e77 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1782,16 +1782,14 @@ "Remember my selection for this widget": "Remember my selection for this widget", "Allow": "Allow", "Deny": "Deny", - "Enter recovery passphrase": "Enter recovery passphrase", + "Security Phrase": "Security Phrase", "Unable to access secret storage. Please verify that you entered the correct recovery passphrase.": "Unable to access secret storage. Please verify that you entered the correct recovery passphrase.", - "Warning: You should only do this on a trusted computer.": "Warning: You should only do this on a trusted computer.", - "Access your secure message history and your cross-signing identity for verifying other sessions by entering your recovery passphrase.": "Access your secure message history and your cross-signing identity for verifying other sessions by entering your recovery passphrase.", - "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options.": "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options.", - "Enter recovery key": "Enter recovery key", + "Enter your Security Phrase or to continue.": "Enter your Security Phrase or to continue.", + "Security Key": "Security Key", "Unable to access secret storage. Please verify that you entered the correct recovery key.": "Unable to access secret storage. Please verify that you entered the correct recovery key.", "This looks like a valid recovery key!": "This looks like a valid recovery key!", "Not a valid recovery key": "Not a valid recovery key", - "Access your secure message history and your cross-signing identity for verifying other sessions by entering your recovery key.": "Access your secure message history and your cross-signing identity for verifying other sessions by entering your recovery key.", + "Use your Security Key to continue.": "Use your Security Key to continue.", "If you've forgotten your recovery key you can .": "If you've forgotten your recovery key you can .", "Restoring keys from backup": "Restoring keys from backup", "Fetching keys from server...": "Fetching keys from server...", @@ -1806,9 +1804,11 @@ "Keys restored": "Keys restored", "Failed to decrypt %(failedCount)s sessions!": "Failed to decrypt %(failedCount)s sessions!", "Successfully restored %(sessionCount)s keys": "Successfully restored %(sessionCount)s keys", + "Enter recovery passphrase": "Enter recovery passphrase", "Warning: you should only set up key backup from a trusted computer.": "Warning: you should only set up key backup from a trusted computer.", "Access your secure message history and set up secure messaging by entering your recovery passphrase.": "Access your secure message history and set up secure messaging by entering your recovery passphrase.", "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options": "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options", + "Enter recovery key": "Enter recovery key", "Warning: You should only set up key backup from a trusted computer.": "Warning: You should only set up key backup from a trusted computer.", "Access your secure message history and set up secure messaging by entering your recovery key.": "Access your secure message history and set up secure messaging by entering your recovery key.", "If you've forgotten your recovery key you can ": "If you've forgotten your recovery key you can ", @@ -2195,7 +2195,6 @@ "Copy": "Copy", "Unable to query secret storage status": "Unable to query secret storage status", "Retry": "Retry", - "You can now verify your other devices, and other users to keep your chats safe.": "You can now verify your other devices, and other users to keep your chats safe.", "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.": "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.", "You can also set up Secure Backup & manage your keys in Settings.": "You can also set up Secure Backup & manage your keys in Settings.", "Set up Secure backup": "Set up Secure backup", @@ -2203,7 +2202,6 @@ "Set a Security Phrase": "Set a Security Phrase", "Confirm Security Phrase": "Confirm Security Phrase", "Save your Security Key": "Save your Security Key", - "You're done!": "You're done!", "Unable to set up secret storage": "Unable to set up secret storage", "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.": "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.", "For maximum security, this should be different from your account password.": "For maximum security, this should be different from your account password.", From 29cdebb611124c14e8a80d79169f76778e974ab8 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 25 Jun 2020 13:26:32 +0100 Subject: [PATCH 093/294] i18n --- src/i18n/strings/en_EN.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d551772e77..1c3c61113a 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1790,7 +1790,6 @@ "This looks like a valid recovery key!": "This looks like a valid recovery key!", "Not a valid recovery key": "Not a valid recovery key", "Use your Security Key to continue.": "Use your Security Key to continue.", - "If you've forgotten your recovery key you can .": "If you've forgotten your recovery key you can .", "Restoring keys from backup": "Restoring keys from backup", "Fetching keys from server...": "Fetching keys from server...", "%(completed)s of %(total)s keys restored": "%(completed)s of %(total)s keys restored", From 0acb35dc23b2d14d6782bce624017ee6f1b0f13b Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 25 Jun 2020 13:48:11 +0100 Subject: [PATCH 094/294] Update end to end tests --- test/end-to-end-tests/src/usecases/signup.js | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/test/end-to-end-tests/src/usecases/signup.js b/test/end-to-end-tests/src/usecases/signup.js index aa9f6b7efa..2772e89fb8 100644 --- a/test/end-to-end-tests/src/usecases/signup.js +++ b/test/end-to-end-tests/src/usecases/signup.js @@ -79,35 +79,19 @@ module.exports = async function signup(session, username, password, homeserver) const acceptButton = await session.query('.mx_InteractiveAuthEntryComponents_termsSubmit'); await acceptButton.click(); - //plow through cross-signing setup by entering arbitrary details - //TODO: It's probably important for the tests to know the passphrase - const xsigningPassphrase = 'a7eaXcjpa9!Yl7#V^h$B^%dovHUVX'; // https://xkcd.com/221/ - let passphraseField = await session.query('.mx_CreateSecretStorageDialog_passPhraseField input'); - await session.replaceInputText(passphraseField, xsigningPassphrase); - await session.delay(1000); // give it a second to analyze our passphrase for security + // Continue with the default (generate a security key) let xsignContButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary'); await xsignContButton.click(); - //repeat passphrase entry - passphraseField = await session.query('.mx_CreateSecretStorageDialog_passPhraseField input'); - await session.replaceInputText(passphraseField, xsigningPassphrase); - await session.delay(1000); // give it a second to analyze our passphrase for security - xsignContButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary'); - await xsignContButton.click(); - //ignore the recovery key //TODO: It's probably important for the tests to know the recovery key const copyButton = await session.query('.mx_CreateSecretStorageDialog_recoveryKeyButtons_copyBtn'); await copyButton.click(); //acknowledge that we copied the recovery key to a safe place - const copyContinueButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_primary'); + const copyContinueButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary'); await copyContinueButton.click(); - //acknowledge that we're done cross-signing setup and our keys are safe - const doneOkButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_primary'); - await doneOkButton.click(); - //wait for registration to finish so the hash gets set //onhashchange better? From 65febd24eb5a706948244c13670d94cebe63c546 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 25 Jun 2020 13:52:38 +0100 Subject: [PATCH 095/294] lint --- test/end-to-end-tests/src/usecases/signup.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/end-to-end-tests/src/usecases/signup.js b/test/end-to-end-tests/src/usecases/signup.js index 2772e89fb8..fd41ef1a71 100644 --- a/test/end-to-end-tests/src/usecases/signup.js +++ b/test/end-to-end-tests/src/usecases/signup.js @@ -80,7 +80,7 @@ module.exports = async function signup(session, username, password, homeserver) await acceptButton.click(); // Continue with the default (generate a security key) - let xsignContButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary'); + const xsignContButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary'); await xsignContButton.click(); //ignore the recovery key @@ -89,7 +89,9 @@ module.exports = async function signup(session, username, password, homeserver) await copyButton.click(); //acknowledge that we copied the recovery key to a safe place - const copyContinueButton = await session.query('.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary'); + const copyContinueButton = await session.query( + '.mx_CreateSecretStorageDialog .mx_Dialog_buttons .mx_Dialog_primary', + ); await copyContinueButton.click(); //wait for registration to finish so the hash gets set From aaff3e0bec17e5139297aa2f93fadd70084a4229 Mon Sep 17 00:00:00 2001 From: XoseM Date: Thu, 25 Jun 2020 13:01:31 +0000 Subject: [PATCH 096/294] Translated using Weblate (Galician) Currently translated at 92.9% (2128 of 2291 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/gl/ --- src/i18n/strings/gl.json | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index 2a4bf15b89..192d7744fa 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -2172,5 +2172,38 @@ "Free": "Gratuíto", "Premium": "Premium", "Premium hosting for organisations Learn more": "Hospedaxe Premium para organizacións Saber máis", - "Find other public servers or use a custom server": "Atopa outros servidores públicos ou usa un servidor personalizado" + "Find other public servers or use a custom server": "Atopa outros servidores públicos ou usa un servidor personalizado", + "Couldn't load page": "Non se puido cargar a páxina", + "You are an administrator of this community. You will not be able to rejoin without an invite from another administrator.": "Administras esta comunidade. Non poderás voltar a unirte sen un convite doutra persoa administradora.", + "Want more than a community? Get your own server": "¿Queres algo máis que unha comunidade? Monta o teu propio servidor", + "This homeserver does not support communities": "Este servidor non soporta comunidades", + "Welcome to %(appName)s": "Benvida a %(appName)s", + "Liberate your communication": "Libera as túas comunicacións", + "Send a Direct Message": "Envía unha Mensaxe Directa", + "Create a Group Chat": "Crear unha Conversa en Grupo", + "Self-verification request": "Solicitude de auto-verificación", + "Riot failed to get the protocol list from the homeserver. The homeserver may be too old to support third party networks.": "Riot non puido obter a lista de protocolos desde o servidor. O servidor podería ser moi antigo para soportar redes de terceiros.", + "Riot failed to get the public room list.": "Riot non puido obter a lista de salas públicas.", + "The homeserver may be unavailable or overloaded.": "O servidor podería non estar dispoñible ou con sobrecarga.", + "delete the address.": "eliminar o enderezo.", + "Preview": "Vista previa", + "View": "Vista", + "Find a room…": "Atopa unha sala…", + "Find a room… (e.g. %(exampleRoom)s)": "Atopa unha sala... (ex. %(exampleRoom)s)", + "Jump to first unread room.": "Vaite a primeira sala non lida.", + "Jump to first invite.": "Vai ó primeiro convite.", + "You have %(count)s unread notifications in a prior version of this room.|other": "Tes %(count)s notificacións non lidas nunha versión previa desta sala.", + "You have %(count)s unread notifications in a prior version of this room.|one": "Tes %(count)s notificacións non lidas nunha versión previa desta sala.", + "Your profile": "Perfil", + "Switch to light mode": "Cambiar a decorado claro", + "Switch to dark mode": "Cambiar a decorado escuro", + "Switch theme": "Cambiar decorado", + "Security & privacy": "Seguridade & privacidade", + "All settings": "Todos os axustes", + "Archived rooms": "Salas arquivadas", + "Feedback": "Comenta", + "Account settings": "Axustes da conta", + "Could not load user profile": "Non se cargou o perfil da usuaria", + "Verify this login": "Verifcar esta conexión", + "Session verified": "Sesión verificada" } From fa3cc28dccb06e31832c0faa96c9690a151dd222 Mon Sep 17 00:00:00 2001 From: Michael Albert Date: Thu, 25 Jun 2020 06:46:04 +0000 Subject: [PATCH 097/294] Translated using Weblate (German) Currently translated at 99.9% (2289 of 2291 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index ee5eb489bc..20ed28be1d 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -2428,8 +2428,8 @@ "Set a room address to easily share your room with other people.": "Vergebe eine Raum-Adresse, um diesen Raum auf einfache Weise mit anderen Personen teilen zu können.", "You've previously used a newer version of Riot with this session. To use this version again with end to end encryption, you will need to sign out and back in again.": "Du hast für diese Sitzung zuvor eine neuere Version von Riot verwendet. Um diese Version mit Ende-zu-Ende-Verschlüsselung wieder zu benutzen, musst du dich erst ab- und dann wieder anmelden.", "Delete the room address %(alias)s and remove %(name)s from the directory?": "Soll die Raum-Adresse %(alias)s gelöscht und %(name)s aus dem Raum-Verzeichnis entfernt werden?", - "Switch to light mode": "Zum Light Mode wechseln", - "Switch to dark mode": "Zum Dark Mode wechseln", + "Switch to light mode": "Zum hellen Thema wechseln", + "Switch to dark mode": "Zum dunklen Thema wechseln", "Switch theme": "Design ändern", "Security & privacy": "Sicherheit & Datenschutz", "All settings": "Alle Einstellungen", @@ -2470,5 +2470,11 @@ "Hey you. You're the best!": "Hey du. Du bist der Beste!", "Message layout": "Nachrichtenlayout", "Compact": "Kompakt", - "Modern": "Modern" + "Modern": "Modern", + "Enable IRC layout option in the appearance tab": "Option für IRC Layout in den Erscheinungsbild-Einstellungen aktivieren", + "Use a system font": "Verwende die System-Schriftart", + "System font name": "System-Schriftart", + "Customise your appearance": "Verändere das Erscheinungsbild", + "Appearance Settings only affect this Riot session.": "Einstellungen zum Erscheinungsbild wirken sich nur auf diese Riot Sitzung aus.", + "The authenticity of this encrypted message can't be guaranteed on this device.": "Die Echtheit dieser verschlüsselten Nachricht kann auf diesem Gerät nicht garantiert werden." } From 2ef2f76de9ab2d6864335b755413d5a3510f191b Mon Sep 17 00:00:00 2001 From: ziriSut Date: Thu, 25 Jun 2020 09:04:25 +0000 Subject: [PATCH 098/294] Translated using Weblate (Kabyle) Currently translated at 20.1% (460 of 2291 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/kab/ --- src/i18n/strings/kab.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/kab.json b/src/i18n/strings/kab.json index 7180e35b1f..b7cf066a21 100644 --- a/src/i18n/strings/kab.json +++ b/src/i18n/strings/kab.json @@ -451,5 +451,15 @@ "Show typing notifications": "Azen ilɣa yettuszemlen", "Room Colour": "Initen n texxamt", "Show developer tools": "Sken ifecka n uneflay", - "Whether or not you're using the Richtext mode of the Rich Text Editor": "Ma yella tseqdaceḍ askar n Richtext n umaẓrag n uḍris anesbaɣur" + "Whether or not you're using the Richtext mode of the Rich Text Editor": "Ama tseqdaceḍ askar-inek.inem n umaẓrag n uḍris anesbaɣur neɣ xaṭi", + "Whether you're using Riot on a device where touch is the primary input mechanism": "Γas ma tseqdaceḍ Riot inek.inem deg yibenk anida asami d ametwi agejdan n unekcum", + "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "Γas ma tseqdaceḍ tamahilt 'breadcrumbs' neɣ xaṭi(avatar nnig tebdert n texxamt)", + "Whether you're using Riot as an installed Progressive Web App": "Γas ma tseqdaceḍ Riot d asnas web n usfari i ibedden", + "The information being sent to us to help make Riot better includes:": "Talɣut i aɣ-d-yettwaznen ɣef tallalt n usnerni n Riot deg-s:", + "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Ma yili asebter-a degs talɣut tummilt, am texxamt neɣ aseqdac neɣ asulay n ugraw, isefka-a ad ttwakksen send ad ttwaznen i uqeddac.", + "Unable to load! Check your network connectivity and try again.": "Yegguma ad d-yali! Senqed tuqqna-inek.inem ɣer uzeṭṭa syen tεerḍeḍ tikkelt-nniḍen.", + "Failure to create room": "Timerna n texxamt ur teddi ara", + "If you cancel now, you won't complete verifying your other session.": "Ma yella teffɣeḍ tura, ur tessawaḍeḍ ara ad tesneqdeḍ akk tiɣimiyin-inek.inem.", + "If you cancel now, you won't complete your operation.": "Ma yella teffɣeḍ tura, tamhelt-ik.im ur tettemmed ara.", + "Show these rooms to non-members on the community page and room list?": "Sken tixxamin-a i wid ur nettekka ara deg usebter n temɣiwent d tebdert n texxamt?" } From a3b38a2b5f0a683c9633a9c7fbb3b4b6264735d1 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 25 Jun 2020 07:14:02 -0600 Subject: [PATCH 099/294] Make LoggedInView a real component because it uses shouldComponentUpdate React demands this. --- src/components/structures/LoggedInView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 1bc656e6a3..9c01480df2 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -123,7 +123,7 @@ interface IState { * * Components mounted below us can access the matrix client via the react context. */ -class LoggedInView extends React.PureComponent { +class LoggedInView extends React.Component { static displayName = 'LoggedInView'; static propTypes = { From 5045c0b135c481950b74d19cdca3c633b7c1387d Mon Sep 17 00:00:00 2001 From: XoseM Date: Thu, 25 Jun 2020 13:12:19 +0000 Subject: [PATCH 100/294] Translated using Weblate (Galician) Currently translated at 93.6% (2145 of 2291 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/gl/ --- src/i18n/strings/gl.json | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index 192d7744fa..8c60398efd 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -2205,5 +2205,22 @@ "Account settings": "Axustes da conta", "Could not load user profile": "Non se cargou o perfil da usuaria", "Verify this login": "Verifcar esta conexión", - "Session verified": "Sesión verificada" + "Session verified": "Sesión verificada", + "Changing your password will reset any end-to-end encryption keys on all of your sessions, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another session before resetting your password.": "Ao cambiar o contrasinal vas restablecer todas as chaves de cifrado das túas sesións, impedindo ler o historial de conversa. Configura a Copia de Apoio das Chaves ou exporta as chaves da sala desde outra sesión antes de restablecer o contrasinal.", + "Your Matrix account on %(serverName)s": "A túa conta Matrix en %(serverName)s", + "Your Matrix account on ": "A túa conta Matrix en ", + "No identity server is configured: add one in server settings to reset your password.": "Non hai un Servidor de Identidade configurado: engade un nos axustes para restablecer o contrasinal.", + "You have been logged out of all sessions and will no longer receive push notifications. To re-enable notifications, sign in again on each device.": "Desconectaches todas as sesións e non recibirás notificacións push. Para reactivalas, conéctate outra vez nos dispositivos.", + "Set a new password": "Novo contrasinal", + "Invalid homeserver discovery response": "Resposta de descubrimento do servidor non válida", + "Failed to get autodiscovery configuration from server": "Fallo ó obter a configuración de autodescubrimento desde o servidor", + "Invalid base_url for m.homeserver": "base_url non válido para m.homeserver", + "Homeserver URL does not appear to be a valid Matrix homeserver": "O URL do servidor non semella ser un servidor Matrix válido", + "Invalid identity server discovery response": "Resposta de descubrimento de identidade do servidor non válida", + "Invalid base_url for m.identity_server": "base_url para m.identity_server non válida", + "Identity server URL does not appear to be a valid identity server": "O URL do servidor de identidade non semella ser un servidor de identidade válido", + "This account has been deactivated.": "Esta conta foi desactivada.", + "Failed to perform homeserver discovery": "Fallo ao intentar o descubrimento do servidor", + "Syncing...": "Sincronizando...", + "Signing In...": "Conectando con..." } From d5cf4bcfa202d7550085dcde7df608dfbebe2edc Mon Sep 17 00:00:00 2001 From: XoseM Date: Thu, 25 Jun 2020 13:18:18 +0000 Subject: [PATCH 101/294] Translated using Weblate (Galician) Currently translated at 93.7% (2146 of 2291 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/gl/ --- src/i18n/strings/gl.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index 8c60398efd..9c430061a3 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -2222,5 +2222,6 @@ "This account has been deactivated.": "Esta conta foi desactivada.", "Failed to perform homeserver discovery": "Fallo ao intentar o descubrimento do servidor", "Syncing...": "Sincronizando...", - "Signing In...": "Conectando con..." + "Signing In...": "Conectando con...", + "If you've joined lots of rooms, this might take a while": "Se te uniches a moitas salas, esto podería levarnos un anaco" } From 41c59cc75e6bce6968e8e841306686663cd6edf9 Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Thu, 25 Jun 2020 14:21:08 +0100 Subject: [PATCH 102/294] Fix deactivated checked checkbox styling --- res/css/views/elements/_StyledCheckbox.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/css/views/elements/_StyledCheckbox.scss b/res/css/views/elements/_StyledCheckbox.scss index aab448605c..60f1bf0277 100644 --- a/res/css/views/elements/_StyledCheckbox.scss +++ b/res/css/views/elements/_StyledCheckbox.scss @@ -77,8 +77,8 @@ limitations under the License. } &:checked:disabled + label > .mx_Checkbox_background { - background-color: $muted-fg-color; - border-color: rgba($muted-fg-color, 0.5); + background-color: $accent-color; + border-color: $accent-color; } } } From 4b952737dcd08988201d7a024604ab992b5d1de9 Mon Sep 17 00:00:00 2001 From: XoseM Date: Thu, 25 Jun 2020 13:18:32 +0000 Subject: [PATCH 103/294] Translated using Weblate (Galician) Currently translated at 93.7% (2147 of 2291 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/gl/ --- src/i18n/strings/gl.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index 9c430061a3..6844317dbe 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -2223,5 +2223,6 @@ "Failed to perform homeserver discovery": "Fallo ao intentar o descubrimento do servidor", "Syncing...": "Sincronizando...", "Signing In...": "Conectando con...", - "If you've joined lots of rooms, this might take a while": "Se te uniches a moitas salas, esto podería levarnos un anaco" + "If you've joined lots of rooms, this might take a while": "Se te uniches a moitas salas, esto podería levarnos un anaco", + "Create account": "Crea unha conta" } From 61618d51628db53bc7175e1c820278930b2eb90c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 25 Jun 2020 15:02:52 +0100 Subject: [PATCH 104/294] tidy up Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/RoomNotifs.js | 5 +++-- src/components/structures/MatrixChat.tsx | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/RoomNotifs.js b/src/RoomNotifs.js index c67acaf314..4614bef378 100644 --- a/src/RoomNotifs.js +++ b/src/RoomNotifs.js @@ -56,10 +56,11 @@ export function countRoomsWithNotif(rooms) { } export function aggregateNotificationCount(rooms) { - return rooms.reduce((result, room, index) => { + return rooms.reduce((result, room) => { const roomNotifState = getRoomNotifsState(room.roomId); const highlight = room.getUnreadNotificationCount('highlight') > 0; - const notificationCount = room.getUnreadNotificationCount(); + // use helper method to include highlights in the previous version of the room + const notificationCount = getUnreadNotificationCount(room); const notifBadges = notificationCount > 0 && shouldShowNotifBadge(roomNotifState); const mentionBadges = highlight && shouldShowMentionBadge(roomNotifState); diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index fa6cd8a4d8..63d2ec6fd9 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1828,7 +1828,9 @@ export default class MatrixChat extends React.PureComponent { } updateStatusIndicator(state: string, prevState: string) { - const notifCount = countRoomsWithNotif(MatrixClientPeg.get().getRooms()).count; + // only count visible rooms to not torment the user with notification counts in rooms they can't see + // it will include highlights from the previous version of the room internally + const notifCount = countRoomsWithNotif(MatrixClientPeg.get().getVisibleRooms()).count; if (PlatformPeg.get()) { PlatformPeg.get().setErrorStatus(state === 'ERROR'); From 648c0c28c2b78521e1b3d9d0987b03cb49a4429c Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 25 Jun 2020 15:36:06 +0100 Subject: [PATCH 105/294] Add placeholder to security phrase input --- .../views/dialogs/secretstorage/AccessSecretStorageDialog.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js index 9d443b49ab..4196256617 100644 --- a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js +++ b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js @@ -158,6 +158,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent { value={this.state.passPhrase} autoFocus={true} autoComplete="new-password" + placeholder={_t("Security Phrase")} /> {keyStatus} Date: Thu, 25 Jun 2020 16:33:07 +0100 Subject: [PATCH 106/294] Remove unused code No reset option here anymore --- .../dialogs/secretstorage/AccessSecretStorageDialog.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js index 4196256617..b76c62323b 100644 --- a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js +++ b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js @@ -21,7 +21,6 @@ import * as sdk from '../../../../index'; import {MatrixClientPeg} from '../../../../MatrixClientPeg'; import { _t } from '../../../../languageHandler'; -import { accessSecretStorage } from '../../../../CrossSigningManager'; /* * Access Secure Secret Storage by requesting the user's passphrase. @@ -55,12 +54,6 @@ export default class AccessSecretStorageDialog extends React.PureComponent { }); } - _onResetRecoveryClick = () => { - // Re-enter the access flow, but resetting storage this time around. - this.props.onFinished(false); - accessSecretStorage(() => {}, /* forceReset = */ true); - } - _onRecoveryKeyChange = (e) => { this.setState({ recoveryKey: e.target.value, From ed634a2bde594bd67212b43b26a013115f1e0528 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 25 Jun 2020 16:35:40 +0100 Subject: [PATCH 107/294] Add StyledRadioGroup to simplify use of StyledRadioButton and use in Appearance Tab Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../views/elements/StyledRadioGroup.tsx | 61 +++++++++++++++++++ .../tabs/user/AppearanceUserSettingsTab.tsx | 29 +++++---- 2 files changed, 75 insertions(+), 15 deletions(-) create mode 100644 src/components/views/elements/StyledRadioGroup.tsx diff --git a/src/components/views/elements/StyledRadioGroup.tsx b/src/components/views/elements/StyledRadioGroup.tsx new file mode 100644 index 0000000000..050a8b7adb --- /dev/null +++ b/src/components/views/elements/StyledRadioGroup.tsx @@ -0,0 +1,61 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from "react"; +import classNames from "classnames"; + +import StyledRadioButton from "./StyledRadioButton"; + +interface IDefinition { + value: T; + className?: string; + disabled?: boolean; + label: React.ReactChild; + description?: React.ReactChild; +} + +interface IProps { + name: string; + className?: string; + definitions: IDefinition[]; + value?: T; // if not provided no options will be selected + onChange(newValue: T); +} + +function StyledRadioGroup({name, definitions, value, className, onChange}: IProps) { + const _onChange = e => { + onChange(e.target.value); + }; + + return + {definitions.map(d => + + {d.label} + + {d.description} + )} + ; +} + +export default StyledRadioGroup; diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx index e935663bbe..42f8cb01de 100644 --- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx @@ -33,6 +33,7 @@ import StyledCheckbox from '../../../elements/StyledCheckbox'; import SettingsFlag from '../../../elements/SettingsFlag'; import Field from '../../../elements/Field'; import EventTilePreview from '../../../elements/EventTilePreview'; +import StyledRadioGroup from "../../../elements/StyledRadioGroup"; interface IProps { } @@ -116,8 +117,7 @@ export default class AppearanceUserSettingsTab extends React.Component): void => { - const newTheme = e.target.value; + private onThemeChange = (newTheme: string): void => { if (this.state.theme === newTheme) return; // doing getValue in the .catch will still return the value we failed to set, @@ -277,19 +277,18 @@ export default class AppearanceUserSettingsTab extends React.Component {_t("Theme")} {systemThemeSection} -
      - {orderedThemes.map(theme => { - return - {theme.name} - ; - })} +
      + ({ + value: t.id, + label: t.name, + disabled: this.state.useSystemTheme, + className: "mx_ThemeSelector_" + t.id, + }))} + onChange={this.onThemeChange} + value={this.state.useSystemTheme ? undefined : this.state.theme} + />
      {customThemeForm} From c920cf784e155f545264be990d5e2d1f3742efb5 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 23 Jun 2020 08:08:48 -0600 Subject: [PATCH 108/294] Create a StaticNotificationState for representative purposes --- src/components/views/rooms/NotificationBadge.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/components/views/rooms/NotificationBadge.tsx b/src/components/views/rooms/NotificationBadge.tsx index 523b5a55cc..6929341845 100644 --- a/src/components/views/rooms/NotificationBadge.tsx +++ b/src/components/views/rooms/NotificationBadge.tsx @@ -141,6 +141,20 @@ export default class NotificationBadge extends React.PureComponent Date: Thu, 25 Jun 2020 16:45:01 +0100 Subject: [PATCH 109/294] Add account and room-account data hooks Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/hooks/useAccountData.ts | 50 +++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/hooks/useAccountData.ts diff --git a/src/hooks/useAccountData.ts b/src/hooks/useAccountData.ts new file mode 100644 index 0000000000..b9b89d1350 --- /dev/null +++ b/src/hooks/useAccountData.ts @@ -0,0 +1,50 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import {useCallback, useState} from "react"; +import {MatrixClient} from "matrix-js-sdk/src/client"; +import {MatrixEvent} from "matrix-js-sdk/src/models/event"; +import {Room} from "matrix-js-sdk/src/models/room"; + +import {useEventEmitter} from "./useEventEmitter"; + +const tryGetContent = (ev?: MatrixEvent) => ev ? ev.getContent() : undefined; + +// Hook to simplify listening to Matrix account data +export const useAccountData = (cli: MatrixClient, eventType: string) => { + const [value, setValue] = useState(() => tryGetContent(cli.getAccountData(eventType))); + + const handler = useCallback((event) => { + if (event.getType() !== eventType) return; + setValue(event.getContent()); + }, [cli, eventType]); + useEventEmitter(cli, "accountData", handler); + + return value || {}; +}; + +// Hook to simplify listening to Matrix room account data +export const useRoomAccountData = (room: Room, eventType: string) => { + const [value, setValue] = useState(() => tryGetContent(room.getAccountData(eventType))); + + const handler = useCallback((event) => { + if (event.getType() !== eventType) return; + setValue(event.getContent()); + }, [room, eventType]); + useEventEmitter(room, "Room.accountData", handler); + + return value || {}; +}; From 4885615a40770ed7823e697e9bc79d9a4aacb790 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 25 Jun 2020 16:55:38 +0100 Subject: [PATCH 110/294] improve typing Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/hooks/useAccountData.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hooks/useAccountData.ts b/src/hooks/useAccountData.ts index b9b89d1350..dd0d53f0d3 100644 --- a/src/hooks/useAccountData.ts +++ b/src/hooks/useAccountData.ts @@ -33,7 +33,7 @@ export const useAccountData = (cli: MatrixClient, eventType: strin }, [cli, eventType]); useEventEmitter(cli, "accountData", handler); - return value || {}; + return value || {} as T; }; // Hook to simplify listening to Matrix room account data @@ -46,5 +46,5 @@ export const useRoomAccountData = (room: Room, eventType: string) }, [room, eventType]); useEventEmitter(room, "Room.accountData", handler); - return value || {}; + return value || {} as T; }; From 5efa5d2c809582be2a51bdd2c6ea377d9a54ebb7 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 25 Jun 2020 10:07:23 -0600 Subject: [PATCH 111/294] Implement new resize handle for dogfooding Smaller handle width, small shadow on the top of the show more button if there's more rooms to be shown. The resize handle also only shows when you're hovering in the area now. The original design called for the shadow to show up only if the user is cutting a tile or dragging, however that is complicated implementation-wise. For speed and encouraging a dogfooding pattern we're going ahead with this behaviour instead. --- res/css/views/rooms/_RoomSublist2.scss | 40 +++++++++++++-------- src/components/views/rooms/RoomSublist2.tsx | 12 +++++-- src/stores/room-list/ListLayout.ts | 14 +++++--- 3 files changed, 44 insertions(+), 22 deletions(-) diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss index c7dae56353..8dfc117533 100644 --- a/res/css/views/rooms/_RoomSublist2.scss +++ b/res/css/views/rooms/_RoomSublist2.scss @@ -226,6 +226,16 @@ limitations under the License. .mx_RoomSublist2_showLessButtonChevron { mask-image: url('$(res)/img/feather-customised/chevron-up.svg'); } + + &.mx_RoomSublist2_isCutting::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 4px; + box-shadow: 0px -2px 3px rgba(46, 47, 50, 0.08); + } } // Class name comes from the ResizableBox component @@ -233,31 +243,31 @@ limitations under the License. // so that selector is below and one level higher. .react-resizable-handle { cursor: ns-resize; - border-radius: 2px; + border-radius: 3px; + + // Update the render() function for RoomSublist2 if this changes + height: 3px; // This is positioned directly below the 'show more' button. position: absolute; bottom: 0; - left: 0; - right: 0; - // This is to visually align the bar in the list. Should be 12px from - // either side of the list. We define this after the positioning to - // trick the browser. - margin-left: 4px; - margin-right: 4px; + // Together, these make the bar 48px wide + left: calc(50% - 24px); + right: calc(50% - 24px); + } + + // TODO: Use less sketchy selector by replacing the resize component entirely + // This causes flickering. + .mx_RoomSublist2_showNButton:hover + .react-resizable-handle, + .react-resizable-handle:hover { + opacity: 0.8; + background-color: $primary-fg-color; } } // The aforementioned selector for the hover state. &:hover, &.mx_RoomSublist2_hasMenuOpen { - .react-resizable-handle { - opacity: 0.2; - - // Update the render() function for RoomSublist2 if this changes - border: 2px solid $primary-fg-color; - } - &:not(.mx_RoomSublist2_minimized) > .mx_RoomSublist2_headerContainer { // If the header doesn't have an aux button we still need to hide the badge for // the menu button. diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index 5cbe10e160..015ad5b646 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -43,7 +43,7 @@ import { TagID } from "../../../stores/room-list/models"; *******************************************************************/ const SHOW_N_BUTTON_HEIGHT = 32; // As defined by CSS -const RESIZE_HANDLE_HEIGHT = 4; // As defined by CSS +const RESIZE_HANDLE_HEIGHT = 3; // As defined by CSS const MAX_PADDING_HEIGHT = SHOW_N_BUTTON_HEIGHT + RESIZE_HANDLE_HEIGHT; @@ -356,6 +356,12 @@ export default class RoomSublist2 extends React.Component { const nVisible = Math.floor(layout.visibleTiles); const visibleTiles = tiles.slice(0, nVisible); + const maxTilesFactored = layout.tilesWithResizerBoxFactor(tiles.length); + const showMoreBtnClasses = classNames({ + 'mx_RoomSublist2_showNButton': true, + 'mx_RoomSublist2_isCutting': layout.visibleTiles < maxTilesFactored, + }); + // If we're hiding rooms, show a 'show more' button to the user. This button // floats above the resize handle, if we have one present. If the user has all // tiles visible, it becomes 'show less'. @@ -370,7 +376,7 @@ export default class RoomSublist2 extends React.Component { ); if (this.props.isMinimized) showMoreText = null; showNButton = ( -
      +
      {/* set by CSS masking */} @@ -386,7 +392,7 @@ export default class RoomSublist2 extends React.Component { ); if (this.props.isMinimized) showLessText = null; showNButton = ( -
      +
      {/* set by CSS masking */} diff --git a/src/stores/room-list/ListLayout.ts b/src/stores/room-list/ListLayout.ts index 370777ef8b..8ca8ad637b 100644 --- a/src/stores/room-list/ListLayout.ts +++ b/src/stores/room-list/ListLayout.ts @@ -18,6 +18,10 @@ import { TagID } from "./models"; const TILE_HEIGHT_PX = 44; +// the .65 comes from the CSS where the show more button is +// mathematically 65% of a tile when floating. +const RESIZER_BOX_FACTOR = 0.65; + interface ISerializedListLayout { numTiles: number; showPreviews: boolean; @@ -77,15 +81,13 @@ export class ListLayout { } public get minVisibleTiles(): number { - // the .65 comes from the CSS where the show more button is - // mathematically 65% of a tile when floating. - return 1.65; + return 1 + RESIZER_BOX_FACTOR; } public get defaultVisibleTiles(): number { // TODO: Remove dogfood flag const val = Number(localStorage.getItem("mx_dogfood_rl_defTiles") || 4); - return val + 0.65; // see minVisibleTiles for where the .65 comes from + return val + RESIZER_BOX_FACTOR; } public calculateTilesToPixelsMin(maxTiles: number, n: number, possiblePadding: number): number { @@ -99,6 +101,10 @@ export class ListLayout { return this.tilesToPixels(Math.min(maxTiles, n)) + padding; } + public tilesWithResizerBoxFactor(n: number): number { + return n + RESIZER_BOX_FACTOR; + } + public tilesWithPadding(n: number, paddingPx: number): number { return this.pixelsToTiles(this.tilesToPixelsWithPadding(n, paddingPx)); } From 0af1507eed14767b28e3acbbab8d505d96d02e18 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 25 Jun 2020 10:54:51 -0600 Subject: [PATCH 112/294] Update sublists for new hover states Fixes https://github.com/vector-im/riot-web/issues/14135 Unblocks https://github.com/vector-im/riot-web/issues/14089 --- res/css/views/rooms/_RoomSublist2.scss | 110 ++++++-------------- src/components/views/rooms/RoomSublist2.tsx | 2 +- 2 files changed, 34 insertions(+), 78 deletions(-) diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss index 8dfc117533..2efedc8cc9 100644 --- a/res/css/views/rooms/_RoomSublist2.scss +++ b/res/css/views/rooms/_RoomSublist2.scss @@ -85,23 +85,24 @@ limitations under the License. // *************************** .mx_RoomSublist2_badgeContainer { - opacity: 0.8; - width: 16px; - margin-right: 5px; // aligns with the room tile's badge - // Create another flexbox row because it's super easy to position the badge this way. display: flex; align-items: center; justify-content: center; + + // Apply the width and margin to the badge so the container doesn't occupy dead space + .mx_NotificationBadge { + width: 16px; + margin-left: 8px; // same as menu+aux buttons + } } - // Both of these buttons are hidden by default until the list is hovered .mx_RoomSublist2_auxButton, .mx_RoomSublist2_menuButton { - width: 0; - margin: 0; - visibility: hidden; + margin-left: 8px; // should be the same as the notification badge position: relative; + width: 24px; + height: 24px; border-radius: 32px; &::before { @@ -118,6 +119,13 @@ limitations under the License. } } + // Hide the menu button by default + .mx_RoomSublist2_menuButton { + visibility: hidden; + width: 0; + margin: 0; + } + .mx_RoomSublist2_auxButton::before { mask-image: url('$(res)/img/feather-customised/plus.svg'); } @@ -142,11 +150,9 @@ limitations under the License. .mx_RoomSublist2_collapseBtn { display: inline-block; position: relative; - - // Default hidden - visibility: hidden; - width: 0; - height: 0; + width: 12px; + height: 12px; + margin-right: 8px; &::before { content: ''; @@ -158,7 +164,7 @@ limitations under the License. mask-position: center; mask-size: contain; mask-repeat: no-repeat; - background: $primary-fg-color; + background-color: $primary-fg-color; mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); } @@ -266,46 +272,12 @@ limitations under the License. } } - // The aforementioned selector for the hover state. - &:hover, &.mx_RoomSublist2_hasMenuOpen { - &:not(.mx_RoomSublist2_minimized) > .mx_RoomSublist2_headerContainer { - // If the header doesn't have an aux button we still need to hide the badge for - // the menu button. - .mx_RoomSublist2_badgeContainer { - // Completely hide the badge - width: 0; - margin: 0; - visibility: hidden; - } - - &:not(.mx_RoomSublist2_headerContainer_withAux) { - // The menu button will be the rightmost button, so make it correctly aligned. - .mx_RoomSublist2_menuButton { - margin-right: 1px; // line it up with the badges on the room tiles - } - } - - // Both of these buttons have circled backgrounds and are visible at this point, - // so make them so. - .mx_RoomSublist2_auxButton, - .mx_RoomSublist2_menuButton { - width: 24px; - height: 24px; - margin-left: 16px; - visibility: visible; - background-color: $roomlist2-button-bg-color; - } - } - - .mx_RoomSublist2_headerContainer { - .mx_RoomSublist2_headerText { - .mx_RoomSublist2_collapseBtn { - visibility: visible; - width: 12px; - height: 12px; - margin-right: 4px; - } - } + &.mx_RoomSublist2_hasMenuOpen, + &:not(.mx_RoomSublist2_minimized) > .mx_RoomSublist2_headerContainer:hover { + .mx_RoomSublist2_menuButton { + visibility: visible; + width: 24px; + margin-left: 8px; } } @@ -354,7 +326,12 @@ limitations under the License. } } - &:hover, &.mx_RoomSublist2_hasMenuOpen { + .mx_RoomSublist2_menuButton { + height: 16px; + } + + &.mx_RoomSublist2_hasMenuOpen, + & > .mx_RoomSublist2_headerContainer:hover { .mx_RoomSublist2_menuButton { visibility: visible; position: absolute; @@ -375,7 +352,7 @@ limitations under the License. } } - .mx_RoomSublist2_headerContainer:not(.mx_RoomSublist2_headerContainer_withAux) { + &.mx_RoomSublist2_headerContainer:not(.mx_RoomSublist2_headerContainer_withAux) { .mx_RoomSublist2_menuButton { bottom: 8px; // align to the middle of name, 40px less than the `bottom` above. } @@ -384,27 +361,6 @@ limitations under the License. } } -// We have a hover style on the room list with no specific list hovered, so account for that -.mx_RoomList2:hover .mx_RoomSublist2:not(.mx_RoomSublist2_minimized), -.mx_RoomSublist2_hasMenuOpen:not(.mx_RoomSublist2_minimized) { - .mx_RoomSublist2_headerContainer_withAux { - .mx_RoomSublist2_badgeContainer { - // Completely hide the badge - width: 0; - margin: 0; - visibility: hidden; - } - - .mx_RoomSublist2_auxButton { - // Show the aux button, but not the list button - width: 24px; - height: 24px; - margin-right: 1px; // line it up with the badges on the room tiles - visibility: visible; - } - } -} - .mx_RoomSublist2_contextMenu { padding: 20px 16px; width: 250px; diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index 015ad5b646..6510251bc5 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -320,8 +320,8 @@ export default class RoomSublist2 extends React.Component { {this.props.label} {this.renderMenu()} - {this.props.isMinimized ? null : addRoomButton} {this.props.isMinimized ? null : badgeContainer} + {this.props.isMinimized ? null : addRoomButton}
      {this.props.isMinimized ? badgeContainer : null} {this.props.isMinimized ? addRoomButton : null} From 3524d678f7f5dd1a842a9a409f3bee0b97b2bd64 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 25 Jun 2020 21:24:24 +0100 Subject: [PATCH 113/294] Fix Welcome.html URLs Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/BasePlatform.ts | 15 ++++++++++----- src/components/views/auth/Welcome.js | 4 ++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index d54dc7dd23..aed063ca32 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -227,6 +227,15 @@ export default abstract class BasePlatform { return url; } + // persist hs url and is url for when the user is returned to the app with the login token + // MUST be called before using URLs from getSSOCallbackUrl, internally called by startSingleSignOn + persistSSODetails(mxClient: MatrixClient) { + localStorage.setItem(HOMESERVER_URL_KEY, mxClient.getHomeserverUrl()); + if (mxClient.getIdentityServerUrl()) { + localStorage.setItem(ID_SERVER_URL_KEY, mxClient.getIdentityServerUrl()); + } + } + /** * Begin Single Sign On flows. * @param {MatrixClient} mxClient the matrix client using which we should start the flow @@ -234,11 +243,7 @@ export default abstract class BasePlatform { * @param {string} fragmentAfterLogin the hash to pass to the app during sso callback. */ startSingleSignOn(mxClient: MatrixClient, loginType: "sso" | "cas", fragmentAfterLogin: string) { - // persist hs url and is url for when the user is returned to the app with the login token - localStorage.setItem(HOMESERVER_URL_KEY, mxClient.getHomeserverUrl()); - if (mxClient.getIdentityServerUrl()) { - localStorage.setItem(ID_SERVER_URL_KEY, mxClient.getIdentityServerUrl()); - } + this.persistSSODetails(mxClient); const callbackUrl = this.getSSOCallbackUrl(fragmentAfterLogin); window.location.href = mxClient.getSsoLoginUrl(callbackUrl.toString(), loginType); // redirect to SSO } diff --git a/src/components/views/auth/Welcome.js b/src/components/views/auth/Welcome.js index 91ba368f70..c01b846739 100644 --- a/src/components/views/auth/Welcome.js +++ b/src/components/views/auth/Welcome.js @@ -45,8 +45,8 @@ export default class Welcome extends React.PureComponent { idBaseUrl: isUrl, }); const plaf = PlatformPeg.get(); - const callbackUrl = plaf.getSSOCallbackUrl(tmpClient.getHomeserverUrl(), tmpClient.getIdentityServerUrl(), - this.props.fragmentAfterLogin); + plaf.persistSSODetails(tmpClient); + const callbackUrl = plaf.getSSOCallbackUrl(this.props.fragmentAfterLogin); return ( From 1c00ae8dd35857bc052b21ee882e863e205e9e2b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 25 Jun 2020 21:59:46 +0100 Subject: [PATCH 114/294] Move to mx_sso_hs_url and co for sso persistance to not conflict with guest creds Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/BasePlatform.ts | 21 ++++++++------------ src/Lifecycle.js | 9 ++++++--- src/components/structures/auth/SoftLogout.js | 6 +++--- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index aed063ca32..1d11495e61 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -25,8 +25,8 @@ import {CheckUpdatesPayload} from "./dispatcher/payloads/CheckUpdatesPayload"; import {Action} from "./dispatcher/actions"; import {hideToast as hideUpdateToast} from "./toasts/UpdateToast"; -export const HOMESERVER_URL_KEY = "mx_hs_url"; -export const ID_SERVER_URL_KEY = "mx_is_url"; +export const SSO_HOMESERVER_URL_KEY = "mx_sso_hs_url"; +export const SSO_ID_SERVER_URL_KEY = "mx_sso_is_url"; export enum UpdateCheckStatus { Checking = "CHECKING", @@ -221,21 +221,12 @@ export default abstract class BasePlatform { setLanguage(preferredLangs: string[]) {} - getSSOCallbackUrl(fragmentAfterLogin: string): URL { + protected getSSOCallbackUrl(fragmentAfterLogin: string): URL { const url = new URL(window.location.href); url.hash = fragmentAfterLogin || ""; return url; } - // persist hs url and is url for when the user is returned to the app with the login token - // MUST be called before using URLs from getSSOCallbackUrl, internally called by startSingleSignOn - persistSSODetails(mxClient: MatrixClient) { - localStorage.setItem(HOMESERVER_URL_KEY, mxClient.getHomeserverUrl()); - if (mxClient.getIdentityServerUrl()) { - localStorage.setItem(ID_SERVER_URL_KEY, mxClient.getIdentityServerUrl()); - } - } - /** * Begin Single Sign On flows. * @param {MatrixClient} mxClient the matrix client using which we should start the flow @@ -243,7 +234,11 @@ export default abstract class BasePlatform { * @param {string} fragmentAfterLogin the hash to pass to the app during sso callback. */ startSingleSignOn(mxClient: MatrixClient, loginType: "sso" | "cas", fragmentAfterLogin: string) { - this.persistSSODetails(mxClient); + // persist hs url and is url for when the user is returned to the app with the login token + localStorage.setItem(SSO_HOMESERVER_URL_KEY, mxClient.getHomeserverUrl()); + if (mxClient.getIdentityServerUrl()) { + localStorage.setItem(SSO_ID_SERVER_URL_KEY, mxClient.getIdentityServerUrl()); + } const callbackUrl = this.getSSOCallbackUrl(fragmentAfterLogin); window.location.href = mxClient.getSsoLoginUrl(callbackUrl.toString(), loginType); // redirect to SSO } diff --git a/src/Lifecycle.js b/src/Lifecycle.js index 96cefaf593..facde3011c 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -41,7 +41,10 @@ import {IntegrationManagers} from "./integrations/IntegrationManagers"; import {Mjolnir} from "./mjolnir/Mjolnir"; import DeviceListener from "./DeviceListener"; import {Jitsi} from "./widgets/Jitsi"; -import {HOMESERVER_URL_KEY, ID_SERVER_URL_KEY} from "./BasePlatform"; +import {SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY} from "./BasePlatform"; + +export const HOMESERVER_URL_KEY = "mx_hs_url"; +export const ID_SERVER_URL_KEY = "mx_is_url"; /** * Called at startup, to attempt to build a logged-in Matrix session. It tries @@ -164,8 +167,8 @@ export function attemptTokenLogin(queryParams, defaultDeviceDisplayName) { return Promise.resolve(false); } - const homeserver = localStorage.getItem(HOMESERVER_URL_KEY); - const identityServer = localStorage.getItem(ID_SERVER_URL_KEY); + const homeserver = localStorage.getItem(SSO_HOMESERVER_URL_KEY); + const identityServer = localStorage.getItem(SSO_ID_SERVER_URL_KEY); if (!homeserver) { console.warn("Cannot log in with token: can't determine HS URL to use"); return Promise.resolve(false); diff --git a/src/components/structures/auth/SoftLogout.js b/src/components/structures/auth/SoftLogout.js index a2824b63a3..6577386fae 100644 --- a/src/components/structures/auth/SoftLogout.js +++ b/src/components/structures/auth/SoftLogout.js @@ -25,7 +25,7 @@ import {MatrixClientPeg} from "../../../MatrixClientPeg"; import {sendLoginRequest} from "../../../Login"; import AuthPage from "../../views/auth/AuthPage"; import SSOButton from "../../views/elements/SSOButton"; -import {HOMESERVER_URL_KEY, ID_SERVER_URL_KEY} from "../../../BasePlatform"; +import {SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY} from "../../../BasePlatform"; const LOGIN_VIEW = { LOADING: 1, @@ -158,8 +158,8 @@ export default class SoftLogout extends React.Component { async trySsoLogin() { this.setState({busy: true}); - const hsUrl = localStorage.getItem(HOMESERVER_URL_KEY); - const isUrl = localStorage.getItem(ID_SERVER_URL_KEY) || MatrixClientPeg.get().getIdentityServerUrl(); + const hsUrl = localStorage.getItem(SSO_HOMESERVER_URL_KEY); + const isUrl = localStorage.getItem(SSO_ID_SERVER_URL_KEY) || MatrixClientPeg.get().getIdentityServerUrl(); const loginType = "m.login.token"; const loginParams = { token: this.props.realQueryParams['loginToken'], From c65ccbcacf1ab3096c405434da6ec91349aebf75 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 25 Jun 2020 22:00:22 +0100 Subject: [PATCH 115/294] Instead of passing sso and cas urls to Welcome, route via start_sso and start_cas Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/MatrixChat.tsx | 26 ++++++++++++++++++++---- src/components/views/auth/Welcome.js | 15 ++------------ 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index d5f73fa3df..a48b1e62a9 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -18,6 +18,7 @@ limitations under the License. */ import React, { createRef } from 'react'; +import { createClient } from "matrix-js-sdk"; import { InvalidStoreError } from "matrix-js-sdk/src/errors"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; @@ -1612,6 +1613,19 @@ export default class MatrixChat extends React.PureComponent { }); } else if (screen === 'directory') { dis.fire(Action.ViewRoomDirectory); + } else if (screen === "start_sso" || screen === "start_cas") { + // TODO if logged in, skip SSO + let cli = MatrixClientPeg.get(); + if (!cli) { + const {hsUrl, isUrl} = this.props.serverConfig; + cli = createClient({ + baseUrl: hsUrl, + idBaseUrl: isUrl, + }); + } + + const type = screen === "start_sso" ? "sso" : "cas"; + PlatformPeg.get().startSingleSignOn(cli, type, this.getFragmentAfterLogin()); } else if (screen === 'groups') { dis.dispatch({ action: 'view_my_groups', @@ -1922,9 +1936,7 @@ export default class MatrixChat extends React.PureComponent { this.onLoggedIn(); }; - render() { - // console.log(`Rendering MatrixChat with view ${this.state.view}`); - + getFragmentAfterLogin() { let fragmentAfterLogin = ""; if (this.props.initialScreenAfterLogin && // XXX: workaround for https://github.com/vector-im/riot-web/issues/11643 causing a login-loop @@ -1932,7 +1944,13 @@ export default class MatrixChat extends React.PureComponent { ) { fragmentAfterLogin = `/${this.props.initialScreenAfterLogin.screen}`; } + return fragmentAfterLogin; + } + render() { + // console.log(`Rendering MatrixChat with view ${this.state.view}`); + + const fragmentAfterLogin = this.getFragmentAfterLogin(); let view; if (this.state.view === Views.LOADING) { @@ -2011,7 +2029,7 @@ export default class MatrixChat extends React.PureComponent { } } else if (this.state.view === Views.WELCOME) { const Welcome = sdk.getComponent('auth.Welcome'); - view = ; + view = ; } else if (this.state.view === Views.REGISTER) { const Registration = sdk.getComponent('structures.auth.Registration'); view = ( diff --git a/src/components/views/auth/Welcome.js b/src/components/views/auth/Welcome.js index c01b846739..5a30a02490 100644 --- a/src/components/views/auth/Welcome.js +++ b/src/components/views/auth/Welcome.js @@ -18,9 +18,7 @@ import React from 'react'; import * as sdk from '../../../index'; import SdkConfig from '../../../SdkConfig'; import AuthPage from "./AuthPage"; -import * as Matrix from "matrix-js-sdk"; import {_td} from "../../../languageHandler"; -import PlatformPeg from "../../../PlatformPeg"; // translatable strings for Welcome pages _td("Sign in with SSO"); @@ -39,15 +37,6 @@ export default class Welcome extends React.PureComponent { pageUrl = 'welcome.html'; } - const {hsUrl, isUrl} = this.props.serverConfig; - const tmpClient = Matrix.createClient({ - baseUrl: hsUrl, - idBaseUrl: isUrl, - }); - const plaf = PlatformPeg.get(); - plaf.persistSSODetails(tmpClient); - const callbackUrl = plaf.getSSOCallbackUrl(this.props.fragmentAfterLogin); - return (
      @@ -55,8 +44,8 @@ export default class Welcome extends React.PureComponent { className="mx_WelcomePage" url={pageUrl} replaceMap={{ - "$riot:ssoUrl": tmpClient.getSsoLoginUrl(callbackUrl.toString(), "sso"), - "$riot:casUrl": tmpClient.getSsoLoginUrl(callbackUrl.toString(), "cas"), + "$riot:ssoUrl": "#/start_sso", + "$riot:casUrl": "#/start_cas", }} /> From f02c52b758439022b1746d0006d2e54dd1163ec0 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 25 Jun 2020 22:01:41 +0100 Subject: [PATCH 116/294] unexport things which need not exporting Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/Lifecycle.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Lifecycle.js b/src/Lifecycle.js index facde3011c..9ae4ae7e03 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -43,8 +43,8 @@ import DeviceListener from "./DeviceListener"; import {Jitsi} from "./widgets/Jitsi"; import {SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY} from "./BasePlatform"; -export const HOMESERVER_URL_KEY = "mx_hs_url"; -export const ID_SERVER_URL_KEY = "mx_is_url"; +const HOMESERVER_URL_KEY = "mx_hs_url"; +const ID_SERVER_URL_KEY = "mx_is_url"; /** * Called at startup, to attempt to build a logged-in Matrix session. It tries From 29b0505bdbbfe21162d4a0c978a06238dc5f0991 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 25 Jun 2020 22:02:39 +0100 Subject: [PATCH 117/294] Welcome no longer needs any props Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/MatrixChat.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index a48b1e62a9..26621fb439 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -2029,7 +2029,7 @@ export default class MatrixChat extends React.PureComponent { } } else if (this.state.view === Views.WELCOME) { const Welcome = sdk.getComponent('auth.Welcome'); - view = ; + view = ; } else if (this.state.view === Views.REGISTER) { const Registration = sdk.getComponent('structures.auth.Registration'); view = ( From 2b58875c7f3859241ea811cf179d59b998cdeaa4 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 25 Jun 2020 15:07:38 -0600 Subject: [PATCH 118/294] Fix alignment issues with the user menu objects --- res/css/structures/_LeftPanel2.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/res/css/structures/_LeftPanel2.scss b/res/css/structures/_LeftPanel2.scss index dd28a3107c..0765b628f6 100644 --- a/res/css/structures/_LeftPanel2.scss +++ b/res/css/structures/_LeftPanel2.scss @@ -48,7 +48,7 @@ $tagPanelWidth: 70px; // only applies in this file, used for calculations flex-direction: column; .mx_LeftPanel2_userHeader { - padding: 14px 12px 20px; // 14px top, 12px sides, 20px bottom + padding: 12px 12px 20px; // 12px top, 12px sides, 20px bottom // Create another flexbox column for the rows to stack within display: flex; @@ -65,6 +65,7 @@ $tagPanelWidth: 70px; // only applies in this file, used for calculations .mx_LeftPanel2_userAvatarContainer { position: relative; // to make default avatars work margin-right: 8px; + height: 32px; // to remove the unknown 4px gap the browser puts below it .mx_LeftPanel2_userAvatar { border-radius: 32px; // should match avatar size From 9f5a716cc5fd82a2af3d46aafab01e4293feecdd Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 25 Jun 2020 15:11:04 -0600 Subject: [PATCH 119/294] Adjust padding and margins on user menu --- res/css/_common.scss | 6 +++--- res/css/structures/_UserMenuButton.scss | 5 ----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/res/css/_common.scss b/res/css/_common.scss index e83c6aaeda..cf48358a4e 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -596,14 +596,14 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { } &:last-child { - padding-bottom: 20px; + padding-bottom: 16px; } } .mx_IconizedContextMenu_optionList { // the notFirst class is for cases where the optionList might be under a header of sorts. &:nth-child(n + 2), .mx_IconizedContextMenu_optionList_notFirst { - margin-top: 20px; + margin-top: 12px; // This is a bit of a hack when we could just use a simple border-top property, // however we have a (kinda) good reason for doing it this way: we need opacity. @@ -634,7 +634,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { li { margin: 0; - padding: 20px 0 0; + padding: 12px 0 0; .mx_AccessibleButton { text-decoration: none; diff --git a/res/css/structures/_UserMenuButton.scss b/res/css/structures/_UserMenuButton.scss index c2bfe5b916..cdb6adb8bd 100644 --- a/res/css/structures/_UserMenuButton.scss +++ b/res/css/structures/_UserMenuButton.scss @@ -45,11 +45,6 @@ limitations under the License. display: flex; align-items: center; - &:nth-child(n + 1) { - // The first header will have appropriate padding, subsequent ones need a margin. - margin-top: 10px; - } - .mx_UserMenuButton_contextMenu_name { // Create another flexbox of columns to handle large user IDs display: flex; From 7b79dd6be14fcc3f7baa36e32e1bdab556f59706 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 25 Jun 2020 15:13:28 -0600 Subject: [PATCH 120/294] Make the sign out button red --- res/css/structures/_UserMenuButton.scss | 10 ++++++++++ src/components/structures/UserMenuButton.tsx | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/res/css/structures/_UserMenuButton.scss b/res/css/structures/_UserMenuButton.scss index cdb6adb8bd..f1dffbd1f5 100644 --- a/res/css/structures/_UserMenuButton.scss +++ b/res/css/structures/_UserMenuButton.scss @@ -40,6 +40,16 @@ limitations under the License. .mx_UserMenuButton_contextMenu { width: 247px; + .mx_UserMenuButton_contextMenu_redRow { + .mx_AccessibleButton { + color: $warning-color !important; // !important to override styles from context menu + } + + .mx_IconizedContextMenu_icon::before { + background-color: $warning-color; + } + } + .mx_UserMenuButton_contextMenu_header { // Create a flexbox to organize the header a bit easier display: flex; diff --git a/src/components/structures/UserMenuButton.tsx b/src/components/structures/UserMenuButton.tsx index 27dfdac5a1..7613a4a9ae 100644 --- a/src/components/structures/UserMenuButton.tsx +++ b/src/components/structures/UserMenuButton.tsx @@ -263,7 +263,7 @@ export default class UserMenuButton extends React.Component {
        -
      • +
      • {_t("Sign out")} From 129ff3a6e043799631ad0c6b2b02695c07abfb1f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 25 Jun 2020 15:17:19 -0600 Subject: [PATCH 121/294] Match line colour from user menu in sublist menu --- res/css/views/rooms/_RoomSublist2.scss | 1 + res/themes/dark/css/_dark.scss | 2 +- res/themes/light/css/_light.scss | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss index 8dfc117533..3117ebcc5f 100644 --- a/res/css/views/rooms/_RoomSublist2.scss +++ b/res/css/views/rooms/_RoomSublist2.scss @@ -414,6 +414,7 @@ limitations under the License. margin-bottom: 16px; margin-right: 16px; // additional 16px border: 1px solid $roomsublist2-divider-color; + opacity: 0.1; } .mx_RoomSublist2_contextMenu_title { diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 69fc91f222..1546e7a400 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -113,7 +113,7 @@ $theme-button-bg-color: #e3e8f0; $roomlist2-button-bg-color: #1A1D23; // Buttons include the filter box, explore button, and sublist buttons $roomlist2-bg-color: $header-panel-bg-color; -$roomsublist2-divider-color: #e9eaeb; +$roomsublist2-divider-color: $primary-fg-color; $roomtile2-preview-color: #9e9e9e; $roomtile2-default-badge-bg-color: #61708b; diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 57dc1fa5e0..c4b4262642 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -180,7 +180,7 @@ $theme-button-bg-color: #e3e8f0; $roomlist2-button-bg-color: #fff; // Buttons include the filter box, explore button, and sublist buttons $roomlist2-bg-color: $header-panel-bg-color; -$roomsublist2-divider-color: #e9eaeb; +$roomsublist2-divider-color: $primary-fg-color; $roomtile2-preview-color: #9e9e9e; $roomtile2-default-badge-bg-color: #61708b; From aacedfaf1380d42d090136aeef737e8170ef42b9 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 25 Jun 2020 15:19:03 -0600 Subject: [PATCH 122/294] Remove opacity from sublist header text, increase weight --- res/css/views/rooms/_RoomSublist2.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss index 3117ebcc5f..5f2e9e093b 100644 --- a/res/css/views/rooms/_RoomSublist2.scss +++ b/res/css/views/rooms/_RoomSublist2.scss @@ -130,9 +130,9 @@ limitations under the License. flex: 1; max-width: calc(100% - 16px); // 16px is the badge width text-transform: uppercase; - opacity: 0.5; line-height: $font-16px; font-size: $font-12px; + font-weight: 600; // Ellipsize any text overflow text-overflow: ellipsis; From 0cb54ed2a401646c8fab2d232f828131dcdac4ee Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 25 Jun 2020 15:42:44 -0600 Subject: [PATCH 123/294] Align the badge count on non-aux lists with other badges --- res/css/views/rooms/_RoomSublist2.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss index 2efedc8cc9..4f1c8e9323 100644 --- a/res/css/views/rooms/_RoomSublist2.scss +++ b/res/css/views/rooms/_RoomSublist2.scss @@ -97,6 +97,12 @@ limitations under the License. } } + &:not(.mx_RoomSublist2_headerContainer_withAux) { + .mx_NotificationBadge { + margin-right: 4px; // just to push it over a bit, aligning it with the other elements + } + } + .mx_RoomSublist2_auxButton, .mx_RoomSublist2_menuButton { margin-left: 8px; // should be the same as the notification badge From 555078a9935ade0ed3b0181a012a0a6a59cf2ef5 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 25 Jun 2020 16:03:56 -0600 Subject: [PATCH 124/294] Iterate on the new room list resize handle Only show shadow when resizing, increase the hit area, and make the handle show up when the list itself is hovered. --- res/css/views/rooms/_RoomSublist2.scss | 21 ++++++++++----------- src/components/views/rooms/RoomSublist2.tsx | 16 ++++++++++++++-- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss index 5f2e9e093b..cbe471e4da 100644 --- a/res/css/views/rooms/_RoomSublist2.scss +++ b/res/css/views/rooms/_RoomSublist2.scss @@ -245,24 +245,23 @@ limitations under the License. cursor: ns-resize; border-radius: 3px; - // Update the render() function for RoomSublist2 if this changes - height: 3px; + // Update RESIZE_HANDLE_HEIGHT if this changes + height: 4px; // This is positioned directly below the 'show more' button. position: absolute; bottom: 0; - // Together, these make the bar 48px wide - left: calc(50% - 24px); - right: calc(50% - 24px); + // Together, these make the bar 64px wide + left: calc(50% - 32px); + right: calc(50% - 32px); } - // TODO: Use less sketchy selector by replacing the resize component entirely - // This causes flickering. - .mx_RoomSublist2_showNButton:hover + .react-resizable-handle, - .react-resizable-handle:hover { - opacity: 0.8; - background-color: $primary-fg-color; + &:hover, &.mx_RoomSublist2_hasMenuOpen { + .react-resizable-handle { + opacity: 0.8; + background-color: $primary-fg-color; + } } } diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index 015ad5b646..7273e80c61 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -43,7 +43,7 @@ import { TagID } from "../../../stores/room-list/models"; *******************************************************************/ const SHOW_N_BUTTON_HEIGHT = 32; // As defined by CSS -const RESIZE_HANDLE_HEIGHT = 3; // As defined by CSS +const RESIZE_HANDLE_HEIGHT = 4; // As defined by CSS const MAX_PADDING_HEIGHT = SHOW_N_BUTTON_HEIGHT + RESIZE_HANDLE_HEIGHT; @@ -70,6 +70,7 @@ interface IProps { interface IState { notificationState: ListNotificationState; menuDisplayed: boolean; + isResizing: boolean; } export default class RoomSublist2 extends React.Component { @@ -82,6 +83,7 @@ export default class RoomSublist2 extends React.Component { this.state = { notificationState: new ListNotificationState(this.props.isInvite, this.props.tagId), menuDisplayed: false, + isResizing: false, }; this.state.notificationState.setRooms(this.props.rooms); } @@ -111,6 +113,14 @@ export default class RoomSublist2 extends React.Component { this.forceUpdate(); // because the layout doesn't trigger a re-render }; + private onResizeStart = () => { + this.setState({isResizing: true}); + }; + + private onResizeStop = () => { + this.setState({isResizing: false}); + }; + private onShowAllClick = () => { this.props.layout.visibleTiles = this.props.layout.tilesWithPadding(this.numTiles, MAX_PADDING_HEIGHT); this.forceUpdate(); // because the layout doesn't trigger a re-render @@ -359,7 +369,7 @@ export default class RoomSublist2 extends React.Component { const maxTilesFactored = layout.tilesWithResizerBoxFactor(tiles.length); const showMoreBtnClasses = classNames({ 'mx_RoomSublist2_showNButton': true, - 'mx_RoomSublist2_isCutting': layout.visibleTiles < maxTilesFactored, + 'mx_RoomSublist2_isCutting': this.state.isResizing && layout.visibleTiles < maxTilesFactored, }); // If we're hiding rooms, show a 'show more' button to the user. This button @@ -438,6 +448,8 @@ export default class RoomSublist2 extends React.Component { resizeHandles={handles} onResize={this.onResize} className="mx_RoomSublist2_resizeBox" + onResizeStart={this.onResizeStart} + onResizeStop={this.onResizeStop} > {visibleTiles} {showNButton} From acf56559e1a0349122cb464058752fc1e4f3f416 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 25 Jun 2020 16:26:07 -0600 Subject: [PATCH 125/294] Introduce an entirely new system for handling message preview copy This reverts earlier changes made to textForEvent as they are no longer needed. This also implements an entire tree of textForEvent-like behaviour as the previews need to be different, which is easiest done with its own stack. --- src/TextForEvent.js | 23 +-- src/components/views/rooms/RoomTile2.tsx | 2 +- src/i18n/strings/en_EN.json | 56 +++++- src/stores/room-list/MessagePreviewStore.ts | 184 ++++++++++++------ .../previews/CallAnswerEventPreview.ts | 35 ++++ .../room-list/previews/CallHangupEvent.ts | 35 ++++ .../previews/CallInviteEventPreview.ts | 39 ++++ .../previews/CreationEventPreview.ts | 31 +++ .../previews/EncryptionEventPreview.ts | 31 +++ .../previews/HistoryVisibilityEventPreview.ts | 42 ++++ src/stores/room-list/previews/IPreview.ts | 31 +++ .../previews/MembershipEventPreview.ts | 90 +++++++++ .../room-list/previews/MessageEventPreview.ts | 55 ++++++ .../room-list/previews/NameEventPreview.ts | 31 +++ .../previews/ReactionEventPreview.ts | 34 ++++ .../room-list/previews/StickerEventPreview.ts | 34 ++++ .../previews/ThirdPartyInviteEventPreview.ts | 42 ++++ .../room-list/previews/TopicEventPreview.ts | 31 +++ src/stores/room-list/previews/utils.ts | 49 +++++ 19 files changed, 799 insertions(+), 76 deletions(-) create mode 100644 src/stores/room-list/previews/CallAnswerEventPreview.ts create mode 100644 src/stores/room-list/previews/CallHangupEvent.ts create mode 100644 src/stores/room-list/previews/CallInviteEventPreview.ts create mode 100644 src/stores/room-list/previews/CreationEventPreview.ts create mode 100644 src/stores/room-list/previews/EncryptionEventPreview.ts create mode 100644 src/stores/room-list/previews/HistoryVisibilityEventPreview.ts create mode 100644 src/stores/room-list/previews/IPreview.ts create mode 100644 src/stores/room-list/previews/MembershipEventPreview.ts create mode 100644 src/stores/room-list/previews/MessageEventPreview.ts create mode 100644 src/stores/room-list/previews/NameEventPreview.ts create mode 100644 src/stores/room-list/previews/ReactionEventPreview.ts create mode 100644 src/stores/room-list/previews/StickerEventPreview.ts create mode 100644 src/stores/room-list/previews/ThirdPartyInviteEventPreview.ts create mode 100644 src/stores/room-list/previews/TopicEventPreview.ts create mode 100644 src/stores/room-list/previews/utils.ts diff --git a/src/TextForEvent.js b/src/TextForEvent.js index 09cfb67de7..3607d7a676 100644 --- a/src/TextForEvent.js +++ b/src/TextForEvent.js @@ -265,22 +265,13 @@ function textForServerACLEvent(ev) { return text + changes.join(" "); } -function textForMessageEvent(ev, skipUserPrefix) { +function textForMessageEvent(ev) { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); let message = senderDisplayName + ': ' + ev.getContent().body; - if (skipUserPrefix) { - message = ev.getContent().body; - if (ev.getContent().msgtype === "m.emote") { - message = senderDisplayName + " " + message; - } else if (ev.getContent().msgtype === "m.image") { - message = _t('sent an image.'); - } - } else { - if (ev.getContent().msgtype === "m.emote") { - message = "* " + senderDisplayName + " " + message; - } else if (ev.getContent().msgtype === "m.image") { - message = _t('%(senderDisplayName)s sent an image.', {senderDisplayName}); - } + if (ev.getContent().msgtype === "m.emote") { + message = "* " + senderDisplayName + " " + message; + } else if (ev.getContent().msgtype === "m.image") { + message = _t('%(senderDisplayName)s sent an image.', {senderDisplayName}); } return message; } @@ -621,8 +612,8 @@ for (const evType of ALL_RULE_TYPES) { stateHandlers[evType] = textForMjolnirEvent; } -export function textForEvent(ev, skipUserPrefix) { +export function textForEvent(ev) { const handler = (ev.isState() ? stateHandlers : handlers)[ev.getType()]; - if (handler) return handler(ev, skipUserPrefix); + if (handler) return handler(ev); return ''; } diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index 63c9c1af23..3d0a555877 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -271,7 +271,7 @@ export default class RoomTile2 extends React.Component { let messagePreview = null; if (this.props.showMessagePreview && !this.props.isMinimized) { // The preview store heavily caches this info, so should be safe to hammer. - const text = MessagePreviewStore.instance.getPreviewForRoom(this.props.room); + const text = MessagePreviewStore.instance.getPreviewForRoom(this.props.room, this.props.tag); // Only show the preview if there is one to show. if (text) { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 74e747726a..86d5f488ff 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -247,7 +247,6 @@ "%(senderDisplayName)s enabled flair for %(groups)s in this room.": "%(senderDisplayName)s enabled flair for %(groups)s in this room.", "%(senderDisplayName)s disabled flair for %(groups)s in this room.": "%(senderDisplayName)s disabled flair for %(groups)s in this room.", "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.": "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.", - "sent an image.": "sent an image.", "%(senderDisplayName)s sent an image.": "%(senderDisplayName)s sent an image.", "%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s set the main address for this room to %(address)s.", "%(senderName)s removed the main address for this room.": "%(senderName)s removed the main address for this room.", @@ -421,12 +420,65 @@ "Restart": "Restart", "Upgrade your Riot": "Upgrade your Riot", "A new version of Riot is available!": "A new version of Riot is available!", - "You: %(message)s": "You: %(message)s", "Guest": "Guest", "There was an error joining the room": "There was an error joining the room", "Sorry, your homeserver is too old to participate in this room.": "Sorry, your homeserver is too old to participate in this room.", "Please contact your homeserver administrator.": "Please contact your homeserver administrator.", "Failed to join room": "Failed to join room", + "You joined the call": "You joined the call", + "%(senderName)s joined the call": "%(senderName)s joined the call", + "Call in progress": "Call in progress", + "You left the call": "You left the call", + "%(senderName)s left the call": "%(senderName)s left the call", + "Call ended": "Call ended", + "You started a call": "You started a call", + "%(senderName)s started a call": "%(senderName)s started a call", + "Waiting for answer": "Waiting for answer", + "%(senderName)s is calling": "%(senderName)s is calling", + "You created the room": "You created the room", + "%(senderName)s created the room": "%(senderName)s created the room", + "You made the chat encrypted": "You made the chat encrypted", + "%(senderName)s made the chat encrypted": "%(senderName)s made the chat encrypted", + "You made history visible to new members": "You made history visible to new members", + "%(senderName)s made history visible to new members": "%(senderName)s made history visible to new members", + "You made history visible to anyone": "You made history visible to anyone", + "%(senderName)s made history visible to anyone": "%(senderName)s made history visible to anyone", + "You made history visible to future members": "You made history visible to future members", + "%(senderName)s made history visible to future members": "%(senderName)s made history visible to future members", + "You were invited": "You were invited", + "%(targetName)s was invited": "%(targetName)s was invited", + "You left": "You left", + "%(targetName)s left": "%(targetName)s left", + "You were kicked (%(reason)s)": "You were kicked (%(reason)s)", + "%(targetName)s was kicked (%(reason)s)": "%(targetName)s was kicked (%(reason)s)", + "You were kicked": "You were kicked", + "%(targetName)s was kicked": "%(targetName)s was kicked", + "You rejected the invite": "You rejected the invite", + "%(targetName)s rejected the invite": "%(targetName)s rejected the invite", + "You were uninvited": "You were uninvited", + "%(targetName)s was uninvited": "%(targetName)s was uninvited", + "You were banned (%(reason)s)": "You were banned (%(reason)s)", + "%(targetName)s was banned (%(reason)s)": "%(targetName)s was banned (%(reason)s)", + "You were banned": "You were banned", + "%(targetName)s was banned": "%(targetName)s was banned", + "You joined": "You joined", + "%(targetName)s joined": "%(targetName)s joined", + "You changed your name": "You changed your name", + "%(targetName)s changed their name": "%(targetName)s changed their name", + "You changed your avatar": "You changed your avatar", + "%(targetName)s changed their avatar": "%(targetName)s changed their avatar", + "%(senderName)s %(emote)s": "%(senderName)s %(emote)s", + "%(senderName)s: %(message)s": "%(senderName)s: %(message)s", + "You changed the room name": "You changed the room name", + "%(senderName)s changed the room name": "%(senderName)s changed the room name", + "%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s", + "%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s", + "You uninvited %(targetName)s": "You uninvited %(targetName)s", + "%(senderName)s uninvited %(targetName)s": "%(senderName)s uninvited %(targetName)s", + "You invited %(targetName)s": "You invited %(targetName)s", + "%(senderName)s invited %(targetName)s": "%(senderName)s invited %(targetName)s", + "You changed the room topic": "You changed the room topic", + "%(senderName)s changed the room topic": "%(senderName)s changed the room topic", "Font scaling": "Font scaling", "Message Pinning": "Message Pinning", "Custom user status messages": "Custom user status messages", diff --git a/src/stores/room-list/MessagePreviewStore.ts b/src/stores/room-list/MessagePreviewStore.ts index 29fa45d882..b727069f9f 100644 --- a/src/stores/room-list/MessagePreviewStore.ts +++ b/src/stores/room-list/MessagePreviewStore.ts @@ -19,32 +19,86 @@ import { ActionPayload } from "../../dispatcher/payloads"; import { AsyncStoreWithClient } from "../AsyncStoreWithClient"; import defaultDispatcher from "../../dispatcher/dispatcher"; import { RoomListStoreTempProxy } from "./RoomListStoreTempProxy"; -import { textForEvent } from "../../TextForEvent"; -import { MatrixEvent } from "matrix-js-sdk/src/models/event"; -import { _t } from "../../languageHandler"; +import { MessageEventPreview } from "./previews/MessageEventPreview"; +import { NameEventPreview } from "./previews/NameEventPreview"; +import { TagID } from "./models"; +import { isNullOrUndefined } from "matrix-js-sdk/src/utils"; +import { TopicEventPreview } from "./previews/TopicEventPreview"; +import { MembershipEventPreview } from "./previews/MembershipEventPreview"; +import { HistoryVisibilityEventPreview } from "./previews/HistoryVisibilityEventPreview"; +import { CallInviteEventPreview } from "./previews/CallInviteEventPreview"; +import { CallAnswerEventPreview } from "./previews/CallAnswerEventPreview"; +import { CallHangupEvent } from "./previews/CallHangupEvent"; +import { EncryptionEventPreview } from "./previews/EncryptionEventPreview"; +import { ThirdPartyInviteEventPreview } from "./previews/ThirdPartyInviteEventPreview"; +import { StickerEventPreview } from "./previews/StickerEventPreview"; +import { ReactionEventPreview } from "./previews/ReactionEventPreview"; +import { CreationEventPreview } from "./previews/CreationEventPreview"; -const PREVIEWABLE_EVENTS = [ - // This is the same list from RiotX - {type: "m.room.message", isState: false}, - {type: "m.room.name", isState: true}, - {type: "m.room.topic", isState: true}, - {type: "m.room.member", isState: true}, - {type: "m.room.history_visibility", isState: true}, - {type: "m.call.invite", isState: false}, - {type: "m.call.hangup", isState: false}, - {type: "m.call.answer", isState: false}, - {type: "m.room.encrypted", isState: false}, - {type: "m.room.encryption", isState: true}, - {type: "m.room.third_party_invite", isState: true}, - {type: "m.sticker", isState: false}, - {type: "m.room.create", isState: true}, -]; +const PREVIEWS = { + 'm.room.message': { + isState: false, + previewer: new MessageEventPreview(), + }, + 'm.room.name': { + isState: true, + previewer: new NameEventPreview(), + }, + 'm.room.topic': { + isState: true, + previewer: new TopicEventPreview(), + }, + 'm.room.member': { + isState: true, + previewer: new MembershipEventPreview(), + }, + 'm.room.history_visibility': { + isState: true, + previewer: new HistoryVisibilityEventPreview(), + }, + 'm.call.invite': { + isState: false, + previewer: new CallInviteEventPreview(), + }, + 'm.call.answer': { + isState: false, + previewer: new CallAnswerEventPreview(), + }, + 'm.call.hangup': { + isState: false, + previewer: new CallHangupEvent(), + }, + 'm.room.encryption': { + isState: true, + previewer: new EncryptionEventPreview(), + }, + 'm.room.third_party_invite': { + isState: true, + previewer: new ThirdPartyInviteEventPreview(), + }, + 'm.sticker': { + isState: false, + previewer: new StickerEventPreview(), + }, + 'm.reaction': { + isState: false, + previewer: new ReactionEventPreview(), + }, + 'm.room.create': { + isState: true, + previewer: new CreationEventPreview(), + }, +}; // The maximum number of events we're willing to look back on to get a preview. const MAX_EVENTS_BACKWARDS = 50; +// type merging ftw +type TAG_ANY = "im.vector.any"; +const TAG_ANY: TAG_ANY = "im.vector.any"; + interface IState { - [roomId: string]: string | null; // null indicates the preview is empty + [roomId: string]: Map; // null indicates the preview is empty / irrelevant } export class MessagePreviewStore extends AsyncStoreWithClient { @@ -61,39 +115,76 @@ export class MessagePreviewStore extends AsyncStoreWithClient { /** * Gets the pre-translated preview for a given room * @param room The room to get the preview for. + * @param inTagId The tag ID in which the room resides * @returns The preview, or null if none present. */ - public getPreviewForRoom(room: Room): string { + public getPreviewForRoom(room: Room, inTagId: TagID): string { if (!room) return null; // invalid room, just return nothing - // It's faster to do a lookup this way than it is to use Object.keys().includes() - // We only want to generate a preview if there's one actually missing and not explicitly - // set as 'none'. const val = this.state[room.roomId]; - if (val !== null && typeof(val) !== "string") { - this.generatePreview(room); - } + if (!val) this.generatePreview(room, inTagId); - return this.state[room.roomId]; + const previews = this.state[room.roomId]; + if (!previews) return null; + + if (!previews.has(inTagId)) { + return previews.get(TAG_ANY); + } + return previews.get(inTagId); } - private generatePreview(room: Room) { + private generatePreview(room: Room, tagId?: TagID) { const events = room.timeline; if (!events) return; // should only happen in tests + let map = this.state[room.roomId]; + if (!map) { + map = new Map(); + + // We set the state later with the map, so no need to send an update now + } + + // Set the tags so we know what to generate + if (!map.has(TAG_ANY)) map.set(TAG_ANY, null); + if (tagId && !map.has(tagId)) map.set(tagId, null); + + let changed = false; for (let i = events.length - 1; i >= 0; i--) { if (i === events.length - MAX_EVENTS_BACKWARDS) return; // limit reached const event = events[i]; - const preview = this.generatePreviewForEvent(event); - if (preview.isPreviewable) { - // noinspection JSIgnoredPromiseFromCall - the AsyncStore handles concurrent calls - this.updateState({[room.roomId]: preview.preview}); - return; // break - we found some text + const previewDef = PREVIEWS[event.getType()]; + if (!previewDef) continue; + if (previewDef.isState && isNullOrUndefined(event.getStateKey())) continue; + + const anyPreview = previewDef.previewer.getTextFor(event, null); + if (!anyPreview) continue; // not previewable for some reason + + changed = changed || anyPreview !== map.get(TAG_ANY); + map.set(TAG_ANY, anyPreview); + + const tagsToGenerate = Array.from(map.keys()).filter(t => t !== TAG_ANY); // we did the any tag above + for (const genTagId of tagsToGenerate) { + const realTagId: TagID = genTagId === TAG_ANY ? null : genTagId; + const preview = previewDef.previewer.getTextFor(event, realTagId); + if (preview === anyPreview) { + changed = changed || anyPreview !== map.get(genTagId); + map.delete(genTagId); + } else { + changed = changed || preview !== map.get(genTagId); + map.set(genTagId, preview); + } } + + if (changed) { + // Update state for good measure - causes emit for update + // noinspection JSIgnoredPromiseFromCall - the AsyncStore handles concurrent calls + this.updateState({[room.roomId]: map}); + } + return; // we're done } - // if we didn't find anything, subscribe ourselves to an update + // At this point, we didn't generate a preview so clear it // noinspection JSIgnoredPromiseFromCall - the AsyncStore handles concurrent calls this.updateState({[room.roomId]: null}); } @@ -107,28 +198,7 @@ export class MessagePreviewStore extends AsyncStoreWithClient { if (payload.action === 'MatrixActions.Room.timeline' || payload.action === 'MatrixActions.Event.decrypted') { const event = payload.event; // TODO: Type out the dispatcher if (!Object.keys(this.state).includes(event.getRoomId())) return; // not important - - const preview = this.generatePreviewForEvent(event); - if (preview.isPreviewable) { - await this.updateState({[event.getRoomId()]: preview.preview}); - return; // break - we found some text - } + this.generatePreview(this.matrixClient.getRoom(event.getRoomId()), TAG_ANY); } } - - private generatePreviewForEvent(event: MatrixEvent): { isPreviewable: boolean, preview: string } { - if (PREVIEWABLE_EVENTS.some(p => p.type === event.getType() && p.isState === event.isState())) { - const isSelf = event.getSender() === this.matrixClient.getUserId(); - let text = textForEvent(event, /*skipUserPrefix=*/isSelf); - if (!text || text.trim().length === 0) text = null; // force null if useless to us - if (text && isSelf) { - // XXX: i18n doesn't really work here if the language doesn't support prefixing. - // We'd ideally somehow route the `You:` bit to the textForEvent call, however - // threading that through is non-trivial. - text = _t("You: %(message)s", {message: text}); - } - return {isPreviewable: true, preview: text}; - } - return {isPreviewable: false, preview: null}; - } } diff --git a/src/stores/room-list/previews/CallAnswerEventPreview.ts b/src/stores/room-list/previews/CallAnswerEventPreview.ts new file mode 100644 index 0000000000..b7207307e2 --- /dev/null +++ b/src/stores/room-list/previews/CallAnswerEventPreview.ts @@ -0,0 +1,35 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { IPreview } from "./IPreview"; +import { TagID } from "../models"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils"; +import { _t } from "../../../languageHandler"; + +export class CallAnswerEventPreview implements IPreview { + public getTextFor(event: MatrixEvent, tagId?: TagID): string { + if (shouldPrefixMessagesIn(event.getRoomId(), tagId)) { + if (isSelf(event)) { + return _t("You joined the call"); + } else { + return _t("%(senderName)s joined the call", {senderName: getSenderName(event)}); + } + } else { + return _t("Call in progress"); + } + } +} diff --git a/src/stores/room-list/previews/CallHangupEvent.ts b/src/stores/room-list/previews/CallHangupEvent.ts new file mode 100644 index 0000000000..adc7d1aac8 --- /dev/null +++ b/src/stores/room-list/previews/CallHangupEvent.ts @@ -0,0 +1,35 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { IPreview } from "./IPreview"; +import { TagID } from "../models"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils"; +import { _t } from "../../../languageHandler"; + +export class CallHangupEvent implements IPreview { + public getTextFor(event: MatrixEvent, tagId?: TagID): string { + if (shouldPrefixMessagesIn(event.getRoomId(), tagId)) { + if (isSelf(event)) { + return _t("You left the call"); + } else { + return _t("%(senderName)s left the call", {senderName: getSenderName(event)}); + } + } else { + return _t("Call ended"); + } + } +} diff --git a/src/stores/room-list/previews/CallInviteEventPreview.ts b/src/stores/room-list/previews/CallInviteEventPreview.ts new file mode 100644 index 0000000000..47486e3701 --- /dev/null +++ b/src/stores/room-list/previews/CallInviteEventPreview.ts @@ -0,0 +1,39 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { IPreview } from "./IPreview"; +import { TagID } from "../models"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils"; +import { _t } from "../../../languageHandler"; + +export class CallInviteEventPreview implements IPreview { + public getTextFor(event: MatrixEvent, tagId?: TagID): string { + if (shouldPrefixMessagesIn(event.getRoomId(), tagId)) { + if (isSelf(event)) { + return _t("You started a call"); + } else { + return _t("%(senderName)s started a call", {senderName: getSenderName(event)}); + } + } else { + if (isSelf(event)) { + return _t("Waiting for answer"); + } else { + return _t("%(senderName)s is calling", {senderName: getSenderName(event)}); + } + } + } +} diff --git a/src/stores/room-list/previews/CreationEventPreview.ts b/src/stores/room-list/previews/CreationEventPreview.ts new file mode 100644 index 0000000000..62bb5fe53a --- /dev/null +++ b/src/stores/room-list/previews/CreationEventPreview.ts @@ -0,0 +1,31 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { IPreview } from "./IPreview"; +import { TagID } from "../models"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { getSenderName, isSelf } from "./utils"; +import { _t } from "../../../languageHandler"; + +export class CreationEventPreview implements IPreview { + public getTextFor(event: MatrixEvent, tagId?: TagID): string { + if (isSelf(event)) { + return _t("You created the room"); + } else { + return _t("%(senderName)s created the room", {senderName: getSenderName(event)}); + } + } +} diff --git a/src/stores/room-list/previews/EncryptionEventPreview.ts b/src/stores/room-list/previews/EncryptionEventPreview.ts new file mode 100644 index 0000000000..d00fd7e7f9 --- /dev/null +++ b/src/stores/room-list/previews/EncryptionEventPreview.ts @@ -0,0 +1,31 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { IPreview } from "./IPreview"; +import { TagID } from "../models"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { getSenderName, isSelf } from "./utils"; +import { _t } from "../../../languageHandler"; + +export class EncryptionEventPreview implements IPreview { + public getTextFor(event: MatrixEvent, tagId?: TagID): string { + if (isSelf(event)) { + return _t("You made the chat encrypted"); + } else { + return _t("%(senderName)s made the chat encrypted", {senderName: getSenderName(event)}); + } + } +} diff --git a/src/stores/room-list/previews/HistoryVisibilityEventPreview.ts b/src/stores/room-list/previews/HistoryVisibilityEventPreview.ts new file mode 100644 index 0000000000..ac77a181f8 --- /dev/null +++ b/src/stores/room-list/previews/HistoryVisibilityEventPreview.ts @@ -0,0 +1,42 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { IPreview } from "./IPreview"; +import { TagID } from "../models"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { getSenderName, isSelf } from "./utils"; +import { _t } from "../../../languageHandler"; + +export class HistoryVisibilityEventPreview implements IPreview { + public getTextFor(event: MatrixEvent, tagId?: TagID): string { + const visibility = event.getContent()['history_visibility']; + const isUs = isSelf(event); + + if (visibility === 'invited' || visibility === 'joined') { + return isUs + ? _t("You made history visible to new members") + : _t("%(senderName)s made history visible to new members", {senderName: getSenderName(event)}); + } else if (visibility === 'world_readable') { + return isUs + ? _t("You made history visible to anyone") + : _t("%(senderName)s made history visible to anyone", {senderName: getSenderName(event)}); + } else { // shared, default + return isUs + ? _t("You made history visible to future members") + : _t("%(senderName)s made history visible to future members", {senderName: getSenderName(event)}); + } + } +} diff --git a/src/stores/room-list/previews/IPreview.ts b/src/stores/room-list/previews/IPreview.ts new file mode 100644 index 0000000000..9beb92bfbf --- /dev/null +++ b/src/stores/room-list/previews/IPreview.ts @@ -0,0 +1,31 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { TagID } from "../models"; + +/** + * Represents an event preview. + */ +export interface IPreview { + /** + * Gets the text which represents the event as a preview. + * @param event The event to preview. + * @param tagId Optional. The tag where the room the event was sent in resides. + * @returns The preview. + */ + getTextFor(event: MatrixEvent, tagId?: TagID): string; +} diff --git a/src/stores/room-list/previews/MembershipEventPreview.ts b/src/stores/room-list/previews/MembershipEventPreview.ts new file mode 100644 index 0000000000..44339aab5f --- /dev/null +++ b/src/stores/room-list/previews/MembershipEventPreview.ts @@ -0,0 +1,90 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { IPreview } from "./IPreview"; +import { TagID } from "../models"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { getTargetName, isSelfTarget } from "./utils"; +import { _t } from "../../../languageHandler"; + +export class MembershipEventPreview implements IPreview { + public getTextFor(event: MatrixEvent, tagId?: TagID): string { + const newMembership = event.getContent()['membership']; + const oldMembership = event.getPrevContent()['membership']; + const reason = event.getContent()['reason']; + const isUs = isSelfTarget(event); + + if (newMembership === 'invite') { + return isUs + ? _t("You were invited") + : _t("%(targetName)s was invited", {targetName: getTargetName(event)}); + } else if (newMembership === 'leave' && oldMembership !== 'invite') { + if (event.getSender() === event.getStateKey()) { + return isUs + ? _t("You left") + : _t("%(targetName)s left", {targetName: getTargetName(event)}); + } else { + if (reason) { + return isUs + ? _t("You were kicked (%(reason)s)", {reason}) + : _t("%(targetName)s was kicked (%(reason)s)", {targetName: getTargetName(event), reason}); + } else { + return isUs + ? _t("You were kicked") + : _t("%(targetName)s was kicked", {targetName: getTargetName(event)}); + } + } + } else if (newMembership === 'leave' && oldMembership === 'invite') { + if (event.getSender() === event.getStateKey()) { + return isUs + ? _t("You rejected the invite") + : _t("%(targetName)s rejected the invite", {targetName: getTargetName(event)}); + } else { + return isUs + ? _t("You were uninvited") + : _t("%(targetName)s was uninvited", {targetName: getTargetName(event)}); + } + } else if (newMembership === 'ban') { + if (reason) { + return isUs + ? _t("You were banned (%(reason)s)", {reason}) + : _t("%(targetName)s was banned (%(reason)s)", {targetName: getTargetName(event), reason}); + } else { + return isUs + ? _t("You were banned") + : _t("%(targetName)s was banned", {targetName: getTargetName(event)}); + } + } else if (newMembership === 'join' && oldMembership !== 'join') { + return isUs + ? _t("You joined") + : _t("%(targetName)s joined", {targetName: getTargetName(event)}); + } else { + const isDisplayNameChange = event.getContent()['displayname'] !== event.getPrevContent()['displayname']; + const isAvatarChange = event.getContent()['avatar_url'] !== event.getPrevContent()['avatar_url']; + if (isDisplayNameChange) { + return isUs + ? _t("You changed your name") + : _t("%(targetName)s changed their name", {targetName: getTargetName(event)}); + } else if (isAvatarChange) { + return isUs + ? _t("You changed your avatar") + : _t("%(targetName)s changed their avatar", {targetName: getTargetName(event)}); + } else { + return null; // no change + } + } + } +} diff --git a/src/stores/room-list/previews/MessageEventPreview.ts b/src/stores/room-list/previews/MessageEventPreview.ts new file mode 100644 index 0000000000..6f0dc14a58 --- /dev/null +++ b/src/stores/room-list/previews/MessageEventPreview.ts @@ -0,0 +1,55 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { IPreview } from "./IPreview"; +import { TagID } from "../models"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { _t } from "../../../languageHandler"; +import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils"; +import ReplyThread from "../../../components/views/elements/ReplyThread"; + +export class MessageEventPreview implements IPreview { + public getTextFor(event: MatrixEvent, tagId?: TagID): string { + let eventContent = event.getContent(); + + if (event.isRelation("m.replace")) { + // It's an edit, generate the preview on the new text + eventContent = event.getContent()['m.new_content']; + } + + let body = (eventContent['body'] || '').trim(); + const msgtype = eventContent['msgtype']; + if (!body || !msgtype) return null; // invalid event, no preview + + // XXX: Newer relations have a getRelation() function which is not compatible with replies. + const mRelatesTo = event.getWireContent()['m.relates_to']; + if (mRelatesTo && mRelatesTo['m.in_reply_to']) { + // If this is a reply, get the real reply and use that + body = (ReplyThread.stripPlainReply(body) || '').trim(); + if (!body) return null; // invalid event, no preview + } + + if (msgtype === 'm.emote') { + return _t("%(senderName)s %(emote)s", {senderName: getSenderName(event), emote: body}); + } + + if (isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) { + return body; + } else { + return _t("%(senderName)s: %(message)s", {senderName: getSenderName(event), message: body}); + } + } +} diff --git a/src/stores/room-list/previews/NameEventPreview.ts b/src/stores/room-list/previews/NameEventPreview.ts new file mode 100644 index 0000000000..4197abacfb --- /dev/null +++ b/src/stores/room-list/previews/NameEventPreview.ts @@ -0,0 +1,31 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { IPreview } from "./IPreview"; +import { TagID } from "../models"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { getSenderName, isSelf } from "./utils"; +import { _t } from "../../../languageHandler"; + +export class NameEventPreview implements IPreview { + public getTextFor(event: MatrixEvent, tagId?: TagID): string { + if (isSelf(event)) { + return _t("You changed the room name"); + } else { + return _t("%(senderName)s changed the room name", {senderName: getSenderName(event)}); + } + } +} diff --git a/src/stores/room-list/previews/ReactionEventPreview.ts b/src/stores/room-list/previews/ReactionEventPreview.ts new file mode 100644 index 0000000000..d58f592feb --- /dev/null +++ b/src/stores/room-list/previews/ReactionEventPreview.ts @@ -0,0 +1,34 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { IPreview } from "./IPreview"; +import { TagID } from "../models"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils"; +import { _t } from "../../../languageHandler"; + +export class ReactionEventPreview implements IPreview { + public getTextFor(event: MatrixEvent, tagId?: TagID): string { + const reaction = event.getRelation().key; + if (!reaction) return; + + if (isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) { + return reaction; + } else { + return _t("%(senderName)s: %(reaction)s", {senderName: getSenderName(event), reaction}); + } + } +} diff --git a/src/stores/room-list/previews/StickerEventPreview.ts b/src/stores/room-list/previews/StickerEventPreview.ts new file mode 100644 index 0000000000..f8263a4a45 --- /dev/null +++ b/src/stores/room-list/previews/StickerEventPreview.ts @@ -0,0 +1,34 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { IPreview } from "./IPreview"; +import { TagID } from "../models"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils"; +import { _t } from "../../../languageHandler"; + +export class StickerEventPreview implements IPreview { + public getTextFor(event: MatrixEvent, tagId?: TagID): string { + const stickerName = event.getContent()['body']; + if (!stickerName) return null; + + if (isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) { + return stickerName; + } else { + return _t("%(senderName)s: %(stickerName)s", {senderName: getSenderName(event), stickerName}); + } + } +} diff --git a/src/stores/room-list/previews/ThirdPartyInviteEventPreview.ts b/src/stores/room-list/previews/ThirdPartyInviteEventPreview.ts new file mode 100644 index 0000000000..b22cd9fac9 --- /dev/null +++ b/src/stores/room-list/previews/ThirdPartyInviteEventPreview.ts @@ -0,0 +1,42 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { IPreview } from "./IPreview"; +import { TagID } from "../models"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { getSenderName, isSelf } from "./utils"; +import { _t } from "../../../languageHandler"; +import { isValid3pidInvite } from "../../../RoomInvite"; + +export class ThirdPartyInviteEventPreview implements IPreview { + public getTextFor(event: MatrixEvent, tagId?: TagID): string { + if (!isValid3pidInvite(event)) { + const targetName = event.getPrevContent().display_name || _t("Someone"); + if (isSelf(event)) { + return _t("You uninvited %(targetName)s", {targetName}); + } else { + return _t("%(senderName)s uninvited %(targetName)s", {senderName: getSenderName(event), targetName}); + } + } else { + const targetName = event.getContent().display_name; + if (isSelf(event)) { + return _t("You invited %(targetName)s", {targetName}); + } else { + return _t("%(senderName)s invited %(targetName)s", {senderName: getSenderName(event), targetName}); + } + } + } +} diff --git a/src/stores/room-list/previews/TopicEventPreview.ts b/src/stores/room-list/previews/TopicEventPreview.ts new file mode 100644 index 0000000000..9b499aae8f --- /dev/null +++ b/src/stores/room-list/previews/TopicEventPreview.ts @@ -0,0 +1,31 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { IPreview } from "./IPreview"; +import { TagID } from "../models"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { getSenderName, isSelf } from "./utils"; +import { _t } from "../../../languageHandler"; + +export class TopicEventPreview implements IPreview { + public getTextFor(event: MatrixEvent, tagId?: TagID): string { + if (isSelf(event)) { + return _t("You changed the room topic"); + } else { + return _t("%(senderName)s changed the room topic", {senderName: getSenderName(event)}); + } + } +} diff --git a/src/stores/room-list/previews/utils.ts b/src/stores/room-list/previews/utils.ts new file mode 100644 index 0000000000..ebbecd7bbd --- /dev/null +++ b/src/stores/room-list/previews/utils.ts @@ -0,0 +1,49 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { MatrixClientPeg } from "../../../MatrixClientPeg"; +import { DefaultTagID, TagID } from "../models"; + +export function isSelf(event: MatrixEvent): boolean { + const selfUserId = MatrixClientPeg.get().getUserId(); + if (event.getType() === 'm.room.member') { + return event.getStateKey() === selfUserId; + } + return event.getSender() === selfUserId; +} + +export function isSelfTarget(event: MatrixEvent): boolean { + const selfUserId = MatrixClientPeg.get().getUserId(); + return event.getStateKey() === selfUserId; +} + +export function shouldPrefixMessagesIn(roomId: string, tagId: TagID): boolean { + if (tagId !== DefaultTagID.DM) return true; + + // We don't prefix anything in 1:1s + const room = MatrixClientPeg.get().getRoom(roomId); + if (!room) return true; + return room.currentState.getJoinedMemberCount() !== 2; +} + +export function getSenderName(event: MatrixEvent): string { + return event.sender ? event.sender.name : event.getSender(); +} + +export function getTargetName(event: MatrixEvent): string { + return event.target ? event.target.name : event.getStateKey(); +} From 6116cfc2b96d753dbbe32b88c0ae8f715ad9a2de Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 25 Jun 2020 23:52:32 +0100 Subject: [PATCH 126/294] js-sdk imports suck Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/MatrixChat.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 26621fb439..7f838f1e9e 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -18,7 +18,7 @@ limitations under the License. */ import React, { createRef } from 'react'; -import { createClient } from "matrix-js-sdk"; +import { createClient } from "matrix-js-sdk/src"; import { InvalidStoreError } from "matrix-js-sdk/src/errors"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; @@ -1948,8 +1948,6 @@ export default class MatrixChat extends React.PureComponent { } render() { - // console.log(`Rendering MatrixChat with view ${this.state.view}`); - const fragmentAfterLogin = this.getFragmentAfterLogin(); let view; From 6ea5dc7b7c550a9edd2c79735c8bba8883841a4e Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Fri, 24 Apr 2020 22:14:41 +0100 Subject: [PATCH 127/294] Change the look of the spinner --- res/css/_common.scss | 4 ++ res/css/views/elements/_InlineSpinner.scss | 8 ++++ res/css/views/elements/_Spinner.scss | 10 +++++ res/img/spinner.svg | 3 ++ src/components/structures/MessagePanel.js | 2 +- src/components/views/elements/AppTile.js | 4 +- .../views/elements/InlineSpinner.js | 10 ++++- .../views/elements/MessageSpinner.js | 35 ----------------- src/components/views/elements/Spinner.js | 38 ++++++++++++------- src/components/views/messages/MAudioBody.js | 3 +- src/components/views/messages/MImageBody.js | 8 +--- src/components/views/messages/MVideoBody.js | 3 +- 12 files changed, 67 insertions(+), 61 deletions(-) create mode 100644 res/img/spinner.svg delete mode 100644 src/components/views/elements/MessageSpinner.js diff --git a/res/css/_common.scss b/res/css/_common.scss index cf48358a4e..c087df04cb 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -428,6 +428,10 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { border-radius: 8px; padding: 0px; box-shadow: none; + + /* Don't show scroll-bars on spinner dialogs */ + overflow-x: hidden; + overflow-y: hidden; } // TODO: Review mx_GeneralButton usage to see if it can use a different class diff --git a/res/css/views/elements/_InlineSpinner.scss b/res/css/views/elements/_InlineSpinner.scss index 612b6209c6..561b6cfb82 100644 --- a/res/css/views/elements/_InlineSpinner.scss +++ b/res/css/views/elements/_InlineSpinner.scss @@ -21,4 +21,12 @@ limitations under the License. .mx_InlineSpinner img { margin: 0px 6px; vertical-align: -3px; + + animation: spin 1s linear infinite; +} + +@keyframes spin { + 100% { + transform: rotate(360deg); + } } diff --git a/res/css/views/elements/_Spinner.scss b/res/css/views/elements/_Spinner.scss index 01b4f23c2c..db9f54b56c 100644 --- a/res/css/views/elements/_Spinner.scss +++ b/res/css/views/elements/_Spinner.scss @@ -23,6 +23,16 @@ limitations under the License. flex: 1; } +.mx_Spinner img { + animation: spin 1s linear infinite; +} + +@keyframes spin { + 100% { + transform: rotate(360deg); + } +} + .mx_MatrixChat_middlePanel .mx_Spinner { height: auto; } diff --git a/res/img/spinner.svg b/res/img/spinner.svg new file mode 100644 index 0000000000..a18140c7e2 --- /dev/null +++ b/res/img/spinner.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index d11fee6360..481741dfd2 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -770,7 +770,7 @@ export default class MessagePanel extends React.Component { topSpinner =
      • ; } if (this.props.forwardPaginating) { - bottomSpinner =
      • ; + bottomSpinner =
      • ; } const style = this.props.hidden ? { display: 'none' } : {}; diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 9129b8fe48..60cd1a2eba 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -29,7 +29,7 @@ import { _t } from '../../../languageHandler'; import * as sdk from '../../../index'; import AppPermission from './AppPermission'; import AppWarning from './AppWarning'; -import MessageSpinner from './MessageSpinner'; +import Spinner from './Spinner'; import WidgetUtils from '../../../utils/WidgetUtils'; import dis from '../../../dispatcher/dispatcher'; import ActiveWidgetStore from '../../../stores/ActiveWidgetStore'; @@ -740,7 +740,7 @@ export default class AppTile extends React.Component { if (this.props.show) { const loadingElement = (
        - +
        ); if (!this.state.hasPermissionToLoad) { diff --git a/src/components/views/elements/InlineSpinner.js b/src/components/views/elements/InlineSpinner.js index ad70471d89..4842cba0c6 100644 --- a/src/components/views/elements/InlineSpinner.js +++ b/src/components/views/elements/InlineSpinner.js @@ -16,6 +16,7 @@ limitations under the License. import React from "react"; import createReactClass from 'create-react-class'; +import {_t} from "../../../languageHandler"; export default createReactClass({ displayName: 'InlineSpinner', @@ -24,10 +25,17 @@ export default createReactClass({ const w = this.props.w || 16; const h = this.props.h || 16; const imgClass = this.props.imgClassName || ""; + const alt = this.props.alt || _t("Loading..."); return (
        - + {alt}
        ); }, diff --git a/src/components/views/elements/MessageSpinner.js b/src/components/views/elements/MessageSpinner.js deleted file mode 100644 index 1775fdd4d7..0000000000 --- a/src/components/views/elements/MessageSpinner.js +++ /dev/null @@ -1,35 +0,0 @@ -/* -Copyright 2017 Vector Creations Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import createReactClass from 'create-react-class'; - -export default createReactClass({ - displayName: 'MessageSpinner', - - render: function() { - const w = this.props.w || 32; - const h = this.props.h || 32; - const imgClass = this.props.imgClassName || ""; - const msg = this.props.msg || "Loading..."; - return ( -
        -
        { msg }
          - -
        - ); - }, -}); diff --git a/src/components/views/elements/Spinner.js b/src/components/views/elements/Spinner.js index b1fe97d5d2..3d5697c72e 100644 --- a/src/components/views/elements/Spinner.js +++ b/src/components/views/elements/Spinner.js @@ -16,19 +16,29 @@ limitations under the License. */ import React from "react"; -import createReactClass from 'create-react-class'; +import PropTypes from "prop-types"; +import {_t} from "../../../languageHandler"; -export default createReactClass({ - displayName: 'Spinner', +const Spinner = ({w = 32, h = 32, imgClassName, alt, message}) => { + return ( +
        + { message &&
        { message}
         
        } + {alt +
        + ); +}; +Spinner.propTypes = { + w: PropTypes.number, + h: PropTypes.number, + imgClassName: PropTypes.string, + alt: PropTypes.string, + message: PropTypes.node, +}; - render: function() { - const w = this.props.w || 32; - const h = this.props.h || 32; - const imgClass = this.props.imgClassName || ""; - return ( -
        - -
        - ); - }, -}); +export default Spinner; diff --git a/src/components/views/messages/MAudioBody.js b/src/components/views/messages/MAudioBody.js index a642936fec..421ec8fc47 100644 --- a/src/components/views/messages/MAudioBody.js +++ b/src/components/views/messages/MAudioBody.js @@ -22,6 +22,7 @@ import MFileBody from './MFileBody'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import { decryptFile } from '../../../utils/DecryptFile'; import { _t } from '../../../languageHandler'; +import InlineSpinner from '../elements/InlineSpinner'; export default class MAudioBody extends React.Component { constructor(props) { @@ -94,7 +95,7 @@ export default class MAudioBody extends React.Component { // Not sure how tall the audio player is so not sure how tall it should actually be. return ( - {content.body} + ); } diff --git a/src/components/views/messages/MImageBody.js b/src/components/views/messages/MImageBody.js index ad238a728e..4d12dcf5d7 100644 --- a/src/components/views/messages/MImageBody.js +++ b/src/components/views/messages/MImageBody.js @@ -26,6 +26,7 @@ import { decryptFile } from '../../../utils/DecryptFile'; import { _t } from '../../../languageHandler'; import SettingsStore from "../../../settings/SettingsStore"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; +import InlineSpinner from '../elements/InlineSpinner'; export default class MImageBody extends React.Component { static propTypes = { @@ -365,12 +366,7 @@ export default class MImageBody extends React.Component { // e2e image hasn't been decrypted yet if (content.file !== undefined && this.state.decryptedUrl === null) { - placeholder = {content.body}; + placeholder = ; } else if (!this.state.imgLoaded) { // Deliberately, getSpinner is left unimplemented here, MStickerBody overides placeholder = this.getPlaceholder(); diff --git a/src/components/views/messages/MVideoBody.js b/src/components/views/messages/MVideoBody.js index 03f345e042..1fba9d2ead 100644 --- a/src/components/views/messages/MVideoBody.js +++ b/src/components/views/messages/MVideoBody.js @@ -23,6 +23,7 @@ import {MatrixClientPeg} from '../../../MatrixClientPeg'; import { decryptFile } from '../../../utils/DecryptFile'; import { _t } from '../../../languageHandler'; import SettingsStore from "../../../settings/SettingsStore"; +import InlineSpinner from '../elements/InlineSpinner'; export default createReactClass({ displayName: 'MVideoBody', @@ -147,7 +148,7 @@ export default createReactClass({ return (
        - {content.body} +
        ); From 87f961df3f99feb0cbcc784a65550bbdbf42c4c5 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Fri, 26 Jun 2020 00:00:30 +0100 Subject: [PATCH 128/294] Put behind a labs flag --- res/css/views/elements/_Spinner.scss | 2 +- .../views/elements/InlineSpinner.js | 15 +++++++++-- src/components/views/elements/Spinner.js | 27 +++++++++++++------ src/i18n/strings/en_EN.json | 1 + src/settings/Settings.js | 6 +++++ 5 files changed, 40 insertions(+), 11 deletions(-) diff --git a/res/css/views/elements/_Spinner.scss b/res/css/views/elements/_Spinner.scss index db9f54b56c..6966a60e52 100644 --- a/res/css/views/elements/_Spinner.scss +++ b/res/css/views/elements/_Spinner.scss @@ -23,7 +23,7 @@ limitations under the License. flex: 1; } -.mx_Spinner img { +.mx_Spinner_spin img { animation: spin 1s linear infinite; } diff --git a/src/components/views/elements/InlineSpinner.js b/src/components/views/elements/InlineSpinner.js index 4842cba0c6..dba2479d57 100644 --- a/src/components/views/elements/InlineSpinner.js +++ b/src/components/views/elements/InlineSpinner.js @@ -17,6 +17,7 @@ limitations under the License. import React from "react"; import createReactClass from 'create-react-class'; import {_t} from "../../../languageHandler"; +import SettingsStore from "../../../settings/SettingsStore"; export default createReactClass({ displayName: 'InlineSpinner', @@ -27,10 +28,20 @@ export default createReactClass({ const imgClass = this.props.imgClassName || ""; const alt = this.props.alt || _t("Loading..."); + let divClass; + let imageSource; + if (SettingsStore.isFeatureEnabled('feature_new_spinner')) { + divClass = "mx_InlineSpinner mx_Spinner_spin"; + imageSource = require("../../../../res/img/spinner.svg"); + } else { + divClass = "mx_InlineSpinner"; + imageSource = require("../../../../res/img/spinner.gif"); + } + return ( -
        +
        { + let divClass; + let imageSource; + if (SettingsStore.isFeatureEnabled('feature_new_spinner')) { + divClass = "mx_Spinner mx_Spinner_spin"; + imageSource = require("../../../../res/img/spinner.svg"); + } else { + divClass = "mx_Spinner"; + imageSource = require("../../../../res/img/spinner.gif"); + } + return ( -
        +
        { message &&
        { message}
         
        } - {alt + {alt
        ); }; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4970a650db..ae44d59c59 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -427,6 +427,7 @@ "Sorry, your homeserver is too old to participate in this room.": "Sorry, your homeserver is too old to participate in this room.", "Please contact your homeserver administrator.": "Please contact your homeserver administrator.", "Failed to join room": "Failed to join room", + "New spinner design": "New spinner design", "Font scaling": "Font scaling", "Message Pinning": "Message Pinning", "Custom user status messages": "Custom user status messages", diff --git a/src/settings/Settings.js b/src/settings/Settings.js index f19b827307..820329f6c6 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -97,6 +97,12 @@ export const SETTINGS = { // // not use this for new settings. // invertedSettingName: "my-negative-setting", // }, + "feature_new_spinner": { + isFeature: true, + displayName: _td("New spinner design"), + supportedLevels: LEVELS_FEATURE, + default: false, + }, "feature_font_scaling": { isFeature: true, displayName: _td("Font scaling"), From b00d822bc0782c07622068af8edc2672fce934e0 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Fri, 26 Jun 2020 01:18:02 +0100 Subject: [PATCH 129/294] Remove alt, use aria-label --- src/components/views/elements/InlineSpinner.js | 3 +-- src/components/views/elements/Spinner.js | 5 ++--- src/components/views/messages/MAudioBody.js | 2 +- src/components/views/messages/MImageBody.js | 2 +- src/components/views/messages/MVideoBody.js | 2 +- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/components/views/elements/InlineSpinner.js b/src/components/views/elements/InlineSpinner.js index dba2479d57..ad88868790 100644 --- a/src/components/views/elements/InlineSpinner.js +++ b/src/components/views/elements/InlineSpinner.js @@ -26,7 +26,6 @@ export default createReactClass({ const w = this.props.w || 16; const h = this.props.h || 16; const imgClass = this.props.imgClassName || ""; - const alt = this.props.alt || _t("Loading..."); let divClass; let imageSource; @@ -45,7 +44,7 @@ export default createReactClass({ width={w} height={h} className={imgClass} - alt={alt} + aria-label={_t("Loading...")} />
        ); diff --git a/src/components/views/elements/Spinner.js b/src/components/views/elements/Spinner.js index c192f7d499..ee4351fed6 100644 --- a/src/components/views/elements/Spinner.js +++ b/src/components/views/elements/Spinner.js @@ -20,7 +20,7 @@ import PropTypes from "prop-types"; import {_t} from "../../../languageHandler"; import SettingsStore from "../../../settings/SettingsStore"; -const Spinner = ({w = 32, h = 32, imgClassName, alt, message}) => { +const Spinner = ({w = 32, h = 32, imgClassName, message}) => { let divClass; let imageSource; if (SettingsStore.isFeatureEnabled('feature_new_spinner')) { @@ -39,7 +39,7 @@ const Spinner = ({w = 32, h = 32, imgClassName, alt, message}) => { width={w} height={h} className={imgClassName} - alt={alt || _t("Loading...")} + aria-label={_t("Loading...")} />
        ); @@ -48,7 +48,6 @@ Spinner.propTypes = { w: PropTypes.number, h: PropTypes.number, imgClassName: PropTypes.string, - alt: PropTypes.string, message: PropTypes.node, }; diff --git a/src/components/views/messages/MAudioBody.js b/src/components/views/messages/MAudioBody.js index 421ec8fc47..37f85a108f 100644 --- a/src/components/views/messages/MAudioBody.js +++ b/src/components/views/messages/MAudioBody.js @@ -95,7 +95,7 @@ export default class MAudioBody extends React.Component { // Not sure how tall the audio player is so not sure how tall it should actually be. return ( - + ); } diff --git a/src/components/views/messages/MImageBody.js b/src/components/views/messages/MImageBody.js index 4d12dcf5d7..c92ae475bf 100644 --- a/src/components/views/messages/MImageBody.js +++ b/src/components/views/messages/MImageBody.js @@ -366,7 +366,7 @@ export default class MImageBody extends React.Component { // e2e image hasn't been decrypted yet if (content.file !== undefined && this.state.decryptedUrl === null) { - placeholder = ; + placeholder = ; } else if (!this.state.imgLoaded) { // Deliberately, getSpinner is left unimplemented here, MStickerBody overides placeholder = this.getPlaceholder(); diff --git a/src/components/views/messages/MVideoBody.js b/src/components/views/messages/MVideoBody.js index 1fba9d2ead..fdc04deffc 100644 --- a/src/components/views/messages/MVideoBody.js +++ b/src/components/views/messages/MVideoBody.js @@ -148,7 +148,7 @@ export default createReactClass({ return (
        - +
        ); From dafce40d1b7f1a5b6bf8f3f69a1034810fa3953d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 25 Jun 2020 19:29:12 -0600 Subject: [PATCH 130/294] Rename UserMenuButton to UserMenu for new scope --- res/css/_components.scss | 2 +- res/css/structures/{_UserMenuButton.scss => _UserMenu.scss} | 0 src/components/structures/LeftPanel2.tsx | 4 ++-- .../structures/{UserMenuButton.tsx => UserMenu.tsx} | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename res/css/structures/{_UserMenuButton.scss => _UserMenu.scss} (100%) rename src/components/structures/{UserMenuButton.tsx => UserMenu.tsx} (99%) diff --git a/res/css/_components.scss b/res/css/_components.scss index 66eb98ea9d..afc40ca0d6 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -30,7 +30,7 @@ @import "./structures/_ToastContainer.scss"; @import "./structures/_TopLeftMenuButton.scss"; @import "./structures/_UploadBar.scss"; -@import "./structures/_UserMenuButton.scss"; +@import "./structures/_UserMenu.scss"; @import "./structures/_ViewSource.scss"; @import "./structures/auth/_CompleteSecurity.scss"; @import "./structures/auth/_Login.scss"; diff --git a/res/css/structures/_UserMenuButton.scss b/res/css/structures/_UserMenu.scss similarity index 100% rename from res/css/structures/_UserMenuButton.scss rename to res/css/structures/_UserMenu.scss diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index 27583f26ee..4731dad1fc 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -24,7 +24,7 @@ import RoomList2 from "../views/rooms/RoomList2"; import { Action } from "../../dispatcher/actions"; import { MatrixClientPeg } from "../../MatrixClientPeg"; import BaseAvatar from '../views/avatars/BaseAvatar'; -import UserMenuButton from "./UserMenuButton"; +import UserMenu from "./UserMenuButton"; import RoomSearch from "./RoomSearch"; import AccessibleButton from "../views/elements/AccessibleButton"; import RoomBreadcrumbs2 from "../views/rooms/RoomBreadcrumbs2"; @@ -184,7 +184,7 @@ export default class LeftPanel2 extends React.Component { let name = {OwnProfileStore.instance.displayName}; let buttons = ( - + ); if (this.props.isMinimized) { diff --git a/src/components/structures/UserMenuButton.tsx b/src/components/structures/UserMenu.tsx similarity index 99% rename from src/components/structures/UserMenuButton.tsx rename to src/components/structures/UserMenu.tsx index 7613a4a9ae..c927e5c7a7 100644 --- a/src/components/structures/UserMenuButton.tsx +++ b/src/components/structures/UserMenu.tsx @@ -44,7 +44,7 @@ interface IState { isDarkTheme: boolean; } -export default class UserMenuButton extends React.Component { +export default class UserMenu extends React.Component { private dispatcherRef: string; private themeWatcherRef: string; private buttonRef: React.RefObject = createRef(); From bcfdd4d98406ed26c3df7b3e10bd0de92ed2a6dd Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 25 Jun 2020 19:38:11 -0600 Subject: [PATCH 131/294] Move all of the UserMenu into the UserMenu component --- res/css/structures/_LeftPanel2.scss | 28 +--------- res/css/structures/_UserMenu.scss | 60 ++++++++++++++++----- src/components/structures/LeftPanel2.tsx | 54 +------------------ src/components/structures/UserMenu.tsx | 67 ++++++++++++++++++------ 4 files changed, 100 insertions(+), 109 deletions(-) diff --git a/res/css/structures/_LeftPanel2.scss b/res/css/structures/_LeftPanel2.scss index 0765b628f6..837a1d1f0d 100644 --- a/res/css/structures/_LeftPanel2.scss +++ b/res/css/structures/_LeftPanel2.scss @@ -54,7 +54,7 @@ $tagPanelWidth: 70px; // only applies in this file, used for calculations display: flex; flex-direction: column; - // There's 2 rows when breadcrumbs are present: the top bit and the breadcrumbs + // This is basically just breadcrumbs. The row above that is handled by the UserMenu .mx_LeftPanel2_headerRow { // Create yet another flexbox, this time within the row, to ensure items stay // aligned correctly. This is also a row-based flexbox. @@ -62,32 +62,6 @@ $tagPanelWidth: 70px; // only applies in this file, used for calculations align-items: center; } - .mx_LeftPanel2_userAvatarContainer { - position: relative; // to make default avatars work - margin-right: 8px; - height: 32px; // to remove the unknown 4px gap the browser puts below it - - .mx_LeftPanel2_userAvatar { - border-radius: 32px; // should match avatar size - } - } - - .mx_LeftPanel2_userName { - font-weight: 600; - font-size: $font-15px; - line-height: $font-20px; - flex: 1; - - // Ellipsize any text overflow - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - } - - .mx_LeftPanel2_headerButtons { - // No special styles: the rest of the layout happens to make it work. - } - .mx_LeftPanel2_breadcrumbsContainer { width: 100%; overflow: hidden; diff --git a/res/css/structures/_UserMenu.scss b/res/css/structures/_UserMenu.scss index f1dffbd1f5..a15eeb6c81 100644 --- a/res/css/structures/_UserMenu.scss +++ b/res/css/structures/_UserMenu.scss @@ -14,6 +14,38 @@ See the License for the specific language governing permissions and limitations under the License. */ +.mx_UserMenu { + // Create a row-based flexbox to ensure items stay aligned correctly. + display: flex; + align-items: center; + + .mx_UserMenu_userAvatarContainer { + position: relative; // to make default avatars work + margin-right: 8px; + height: 32px; // to remove the unknown 4px gap the browser puts below it + + .mx_UserMenu_userAvatar { + border-radius: 32px; // should match avatar size + } + } + + .mx_UserMenu_userName { + font-weight: 600; + font-size: $font-15px; + line-height: $font-20px; + flex: 1; + + // Ellipsize any text overflow + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + + .mx_UserMenu_headerButtons { + // No special styles: the rest of the layout happens to make it work. + } +} + .mx_UserMenuButton { > span { width: 16px; @@ -37,10 +69,10 @@ limitations under the License. } } -.mx_UserMenuButton_contextMenu { +.mx_UserMenu_contextMenu { width: 247px; - .mx_UserMenuButton_contextMenu_redRow { + .mx_UserMenu_contextMenu_redRow { .mx_AccessibleButton { color: $warning-color !important; // !important to override styles from context menu } @@ -50,12 +82,12 @@ limitations under the License. } } - .mx_UserMenuButton_contextMenu_header { + .mx_UserMenu_contextMenu_header { // Create a flexbox to organize the header a bit easier display: flex; align-items: center; - .mx_UserMenuButton_contextMenu_name { + .mx_UserMenu_contextMenu_name { // Create another flexbox of columns to handle large user IDs display: flex; flex-direction: column; @@ -72,19 +104,19 @@ limitations under the License. white-space: nowrap; } - .mx_UserMenuButton_contextMenu_displayName { + .mx_UserMenu_contextMenu_displayName { font-weight: bold; font-size: $font-15px; line-height: $font-20px; } - .mx_UserMenuButton_contextMenu_userId { + .mx_UserMenu_contextMenu_userId { font-size: $font-15px; line-height: $font-24px; } } - .mx_UserMenuButton_contextMenu_themeButton { + .mx_UserMenu_contextMenu_themeButton { min-width: 32px; max-width: 32px; width: 32px; @@ -118,31 +150,31 @@ limitations under the License. } } - .mx_UserMenuButton_iconHome::before { + .mx_UserMenu_iconHome::before { mask-image: url('$(res)/img/feather-customised/home.svg'); } - .mx_UserMenuButton_iconBell::before { + .mx_UserMenu_iconBell::before { mask-image: url('$(res)/img/feather-customised/notifications.svg'); } - .mx_UserMenuButton_iconLock::before { + .mx_UserMenu_iconLock::before { mask-image: url('$(res)/img/feather-customised/lock.svg'); } - .mx_UserMenuButton_iconSettings::before { + .mx_UserMenu_iconSettings::before { mask-image: url('$(res)/img/feather-customised/settings.svg'); } - .mx_UserMenuButton_iconArchive::before { + .mx_UserMenu_iconArchive::before { mask-image: url('$(res)/img/feather-customised/archive.svg'); } - .mx_UserMenuButton_iconMessage::before { + .mx_UserMenu_iconMessage::before { mask-image: url('$(res)/img/feather-customised/message-circle.svg'); } - .mx_UserMenuButton_iconSignOut::before { + .mx_UserMenu_iconSignOut::before { mask-image: url('$(res)/img/feather-customised/sign-out.svg'); } } diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index 4731dad1fc..32d6748f94 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -22,18 +22,13 @@ import dis from "../../dispatcher/dispatcher"; import { _t } from "../../languageHandler"; import RoomList2 from "../views/rooms/RoomList2"; import { Action } from "../../dispatcher/actions"; -import { MatrixClientPeg } from "../../MatrixClientPeg"; -import BaseAvatar from '../views/avatars/BaseAvatar'; -import UserMenu from "./UserMenuButton"; +import UserMenu from "./UserMenu"; import RoomSearch from "./RoomSearch"; import AccessibleButton from "../views/elements/AccessibleButton"; import RoomBreadcrumbs2 from "../views/rooms/RoomBreadcrumbs2"; import { BreadcrumbsStore } from "../../stores/BreadcrumbsStore"; import { UPDATE_EVENT } from "../../stores/AsyncStore"; import ResizeNotifier from "../../utils/ResizeNotifier"; -import { MatrixEvent } from "matrix-js-sdk/src/models/event"; -import { throttle } from 'lodash'; -import { OwnProfileStore } from "../../stores/OwnProfileStore"; /******************************************************************* * CAUTION * @@ -76,32 +71,13 @@ export default class LeftPanel2 extends React.Component { // We watch the middle panel because we don't actually get resized, the middle panel does. // We listen to the noisy channel to avoid choppy reaction times. this.props.resizeNotifier.on("middlePanelResizedNoisy", this.onResize); - - OwnProfileStore.instance.on(UPDATE_EVENT, this.onProfileUpdate); } public componentWillUnmount() { BreadcrumbsStore.instance.off(UPDATE_EVENT, this.onBreadcrumbsUpdate); this.props.resizeNotifier.off("middlePanelResizedNoisy", this.onResize); - OwnProfileStore.instance.off(UPDATE_EVENT, this.onProfileUpdate); } - // TSLint wants this to be a member, but we don't want that. - // tslint:disable-next-line - private onRoomStateUpdate = throttle((ev: MatrixEvent) => { - const myUserId = MatrixClientPeg.get().getUserId(); - if (ev.getType() === 'm.room.member' && ev.getSender() === myUserId && ev.getStateKey() === myUserId) { - // noinspection JSIgnoredPromiseFromCall - this.onProfileUpdate(); - } - }, 200, {trailing: true, leading: true}); - - private onProfileUpdate = async () => { - // the store triggered an update, so force a layout update. We don't - // have any state to store here for that to magically happen. - this.forceUpdate(); - }; - private onSearch = (term: string): void => { this.setState({searchFilter: term}); }; @@ -170,7 +146,6 @@ export default class LeftPanel2 extends React.Component { // TODO: Presence // TODO: Breadcrumbs toggle // TODO: Menu button - const avatarSize = 32; // should match border-radius of the avatar let breadcrumbs; if (this.state.showBreadcrumbs) { @@ -181,34 +156,9 @@ export default class LeftPanel2 extends React.Component { ); } - let name = {OwnProfileStore.instance.displayName}; - let buttons = ( - - - - ); - if (this.props.isMinimized) { - name = null; - buttons = null; - } - return (
        -
        - - - - {name} - {buttons} -
        + {breadcrumbs}
        ); diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index c927e5c7a7..89593aa702 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -35,8 +35,10 @@ import SdkConfig from "../../SdkConfig"; import {getHomePageUrl} from "../../utils/pages"; import { OwnProfileStore } from "../../stores/OwnProfileStore"; import { UPDATE_EVENT } from "../../stores/AsyncStore"; +import BaseAvatar from '../views/avatars/BaseAvatar'; interface IProps { + isMinimized: boolean; } interface IState { @@ -158,14 +160,14 @@ export default class UserMenu extends React.Component { defaultDispatcher.dispatch({action: 'view_home_page'}); }; - public render() { + private renderMenuButton(): React.ReactNode { let contextMenu; if (this.state.menuDisplayed) { let hostingLink; const signupLink = getHostingLink("user-context-menu"); if (signupLink) { hostingLink = ( -
        +
        {_t( "Upgrade to your own domain", {}, { @@ -188,7 +190,7 @@ export default class UserMenu extends React.Component { homeButton = (
      • - + {_t("Home")}
      • @@ -203,18 +205,18 @@ export default class UserMenu extends React.Component { top={elementRect.top + elementRect.height} onFinished={this.onCloseMenu} > -
        -
        -
        - +
        +
        +
        + {OwnProfileStore.instance.displayName} - + {MatrixClientPeg.get().getUserId()}
        @@ -231,31 +233,31 @@ export default class UserMenu extends React.Component { {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")}
      • @@ -263,9 +265,9 @@ export default class UserMenu extends React.Component {
          -
        • +
        • - + {_t("Sign out")}
        • @@ -291,4 +293,37 @@ export default class UserMenu extends React.Component { ); } + + public render() { + const avatarSize = 32; // should match border-radius of the avatar + + let name = {OwnProfileStore.instance.displayName}; + let buttons = ( + + {this.renderMenuButton()} + + ); + if (this.props.isMinimized) { + name = null; + buttons = null; + } + + return ( +
          + + + + {name} + {buttons} +
          + ); + } } From 411271422c9f33e802a167074908037ca02a96e9 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 25 Jun 2020 19:54:17 -0600 Subject: [PATCH 132/294] Make the whole UserMenu a button to open the menu --- res/css/structures/_LeftPanel2.scss | 10 - res/css/structures/_UserMenu.scss | 76 ++++--- src/components/structures/UserMenu.tsx | 293 +++++++++++++------------ 3 files changed, 193 insertions(+), 186 deletions(-) diff --git a/res/css/structures/_LeftPanel2.scss b/res/css/structures/_LeftPanel2.scss index 837a1d1f0d..98f23a058b 100644 --- a/res/css/structures/_LeftPanel2.scss +++ b/res/css/structures/_LeftPanel2.scss @@ -132,16 +132,6 @@ $tagPanelWidth: 70px; // only applies in this file, used for calculations .mx_LeftPanel2_roomListContainer { width: 68px; - .mx_LeftPanel2_userHeader { - .mx_LeftPanel2_headerRow { - justify-content: center; - } - - .mx_LeftPanel2_userAvatarContainer { - margin-right: 0; - } - } - .mx_LeftPanel2_filterContainer { // Organize the flexbox into a centered column layout flex-direction: column; diff --git a/res/css/structures/_UserMenu.scss b/res/css/structures/_UserMenu.scss index a15eeb6c81..bbb1e1cc7b 100644 --- a/res/css/structures/_UserMenu.scss +++ b/res/css/structures/_UserMenu.scss @@ -15,39 +15,7 @@ limitations under the License. */ .mx_UserMenu { - // Create a row-based flexbox to ensure items stay aligned correctly. - display: flex; - align-items: center; - - .mx_UserMenu_userAvatarContainer { - position: relative; // to make default avatars work - margin-right: 8px; - height: 32px; // to remove the unknown 4px gap the browser puts below it - - .mx_UserMenu_userAvatar { - border-radius: 32px; // should match avatar size - } - } - - .mx_UserMenu_userName { - font-weight: 600; - font-size: $font-15px; - line-height: $font-20px; - flex: 1; - - // Ellipsize any text overflow - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - } - .mx_UserMenu_headerButtons { - // No special styles: the rest of the layout happens to make it work. - } -} - -.mx_UserMenuButton { - > span { width: 16px; height: 16px; position: relative; @@ -67,6 +35,50 @@ limitations under the License. mask-image: url('$(res)/img/feather-customised/more-horizontal.svg'); } } + + .mx_UserMenu_row { + // Create a row-based flexbox to ensure items stay aligned correctly. + display: flex; + align-items: center; + + .mx_UserMenu_userAvatarContainer { + position: relative; // to make default avatars work + margin-right: 8px; + height: 32px; // to remove the unknown 4px gap the browser puts below it + + .mx_UserMenu_userAvatar { + border-radius: 32px; // should match avatar size + } + } + + .mx_UserMenu_userName { + font-weight: 600; + font-size: $font-15px; + line-height: $font-20px; + flex: 1; + + // Ellipsize any text overflow + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + + .mx_UserMenu_headerButtons { + // No special styles: the rest of the layout happens to make it work. + } + } + + &.mx_UserMenu_minimized { + .mx_UserMenu_userHeader { + .mx_UserMenu_row { + justify-content: center; + } + + .mx_UserMenu_userAvatarContainer { + margin-right: 0; + } + } + } } .mx_UserMenu_contextMenu { diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 89593aa702..6e3670447e 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -36,6 +36,7 @@ import {getHomePageUrl} from "../../utils/pages"; import { OwnProfileStore } from "../../stores/OwnProfileStore"; import { UPDATE_EVENT } from "../../stores/AsyncStore"; import BaseAvatar from '../views/avatars/BaseAvatar'; +import classNames from "classnames"; interface IProps { isMinimized: boolean; @@ -108,7 +109,9 @@ export default class UserMenu extends React.Component { this.setState({menuDisplayed: true}); }; - private onCloseMenu = () => { + private onCloseMenu = (ev: InputEvent) => { + ev.preventDefault(); + ev.stopPropagation(); this.setState({menuDisplayed: false}); }; @@ -160,147 +163,132 @@ export default class UserMenu extends React.Component { defaultDispatcher.dispatch({action: 'view_home_page'}); }; - private renderMenuButton(): React.ReactNode { - let contextMenu; - if (this.state.menuDisplayed) { - let hostingLink; - const signupLink = getHostingLink("user-context-menu"); - if (signupLink) { - hostingLink = ( -
          - {_t( - "Upgrade to your own domain", {}, - { - a: sub => ( - {sub} - ), - }, - )} -
          - ); - } + private renderContextMenu = (): React.ReactNode => { + if (!this.state.menuDisplayed) return null; - let homeButton = null; - if (this.hasHomePage) { - homeButton = ( -
        • - - - {_t("Home")} - -
        • - ); - } - - const elementRect = this.buttonRef.current.getBoundingClientRect(); - contextMenu = ( - -
          -
          -
          - - {OwnProfileStore.instance.displayName} - - - {MatrixClientPeg.get().getUserId()} - -
          -
          - {_t("Switch -
          -
          - {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")} - -
          • -
          -
          -
          -
            -
          • - - - {_t("Sign out")} - -
          • -
          -
          -
          -
          + let hostingLink; + const signupLink = getHostingLink("user-context-menu"); + if (signupLink) { + hostingLink = ( +
          + {_t( + "Upgrade to your own domain", {}, + { + a: sub => ( + {sub} + ), + }, + )} +
          ); } + let homeButton = null; + if (this.hasHomePage) { + homeButton = ( +
        • + + + {_t("Home")} + +
        • + ); + } + + const elementRect = this.buttonRef.current.getBoundingClientRect(); return ( - - - {/* masked image in CSS */} - - {contextMenu} - + +
          +
          +
          + + {OwnProfileStore.instance.displayName} + + + {MatrixClientPeg.get().getUserId()} + +
          +
          + {_t("Switch +
          +
          + {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")} + +
          • +
          +
          +
          +
            +
          • + + + {_t("Sign out")} + +
          • +
          +
          +
          +
          ); - } + }; public render() { + console.log(this.state); const avatarSize = 32; // should match border-radius of the avatar let name = {OwnProfileStore.instance.displayName}; let buttons = ( - {this.renderMenuButton()} + {/* masked image in CSS */} ); if (this.props.isMinimized) { @@ -308,22 +296,39 @@ export default class UserMenu extends React.Component { buttons = null; } + const classes = classNames({ + 'mx_UserMenu': true, + 'mx_UserMenu_minimized': this.props.isMinimized, + }); + return ( -
          - - - - {name} - {buttons} -
          + + +
          + + + + {name} + {buttons} +
          + {this.renderContextMenu()} +
          +
          + ); } } From 588fea3a9b7d8616b7dd4db18bcd4b817231183a Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 25 Jun 2020 19:55:08 -0600 Subject: [PATCH 133/294] Make the menu show up where it was before --- src/components/structures/UserMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 6e3670447e..a824971761 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -204,7 +204,7 @@ export default class UserMenu extends React.Component { return ( From 1888cda5eef0c06e6b70d381515fd3c6e2a0dc1c Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 25 Jun 2020 20:22:41 -0600 Subject: [PATCH 134/294] Remove debug --- src/components/structures/UserMenu.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index a824971761..540c8388d1 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -282,7 +282,6 @@ export default class UserMenu extends React.Component { }; public render() { - console.log(this.state); const avatarSize = 32; // should match border-radius of the avatar let name = {OwnProfileStore.instance.displayName}; From 555758d3d2b037256f28c10ba30fa19a0d4ebdd1 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 25 Jun 2020 20:23:37 -0600 Subject: [PATCH 135/294] Remove extra space --- src/components/structures/UserMenu.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 540c8388d1..19e57ac51b 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -327,7 +327,6 @@ export default class UserMenu extends React.Component { {this.renderContextMenu()} - ); } } From 7ce3cc1db7f43dafbfa556e44c93974e850b4f18 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 25 Jun 2020 20:35:40 -0600 Subject: [PATCH 136/294] Allow the tag panel to be disabled in the new room list Fixes https://github.com/vector-im/riot-web/issues/14156 --- res/css/structures/_LeftPanel2.scss | 13 ++++++++++++- src/components/structures/LeftPanel2.tsx | 11 ++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/res/css/structures/_LeftPanel2.scss b/res/css/structures/_LeftPanel2.scss index 0765b628f6..4bd2f20c89 100644 --- a/res/css/structures/_LeftPanel2.scss +++ b/res/css/structures/_LeftPanel2.scss @@ -38,6 +38,12 @@ $tagPanelWidth: 70px; // only applies in this file, used for calculations // TagPanel handles its own CSS } + &:not(.mx_LeftPanel2_hasTagPanel) { + .mx_LeftPanel2_roomListContainer { + width: 100%; + } + } + // Note: The 'room list' in this context is actually everything that isn't the tag // panel, such as the menu options, breadcrumbs, filtering, etc .mx_LeftPanel2_roomListContainer { @@ -153,7 +159,12 @@ $tagPanelWidth: 70px; // only applies in this file, used for calculations min-width: unset; // We have to forcefully set the width to override the resizer's style attribute. - width: calc(68px + $tagPanelWidth) !important; + &.mx_LeftPanel2_hasTagPanel { + width: calc(68px + $tagPanelWidth) !important; + } + &:not(.mx_LeftPanel2_hasTagPanel) { + width: 68px !important; + } .mx_LeftPanel2_roomListContainer { width: 68px; diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index 27583f26ee..9d86fa2b7c 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -34,6 +34,7 @@ import ResizeNotifier from "../../utils/ResizeNotifier"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { throttle } from 'lodash'; import { OwnProfileStore } from "../../stores/OwnProfileStore"; +import SettingsStore from "../../settings/SettingsStore"; /******************************************************************* * CAUTION * @@ -51,10 +52,12 @@ interface IProps { interface IState { searchFilter: string; // TODO: Move search into room list? showBreadcrumbs: boolean; + showTagPanel: boolean; } export default class LeftPanel2 extends React.Component { private listContainerRef: React.RefObject = createRef(); + private tagPanelWatcherRef: string; // TODO: Properly support TagPanel // TODO: Properly support searching/filtering @@ -69,9 +72,13 @@ export default class LeftPanel2 extends React.Component { this.state = { searchFilter: "", showBreadcrumbs: BreadcrumbsStore.instance.visible, + showTagPanel: SettingsStore.getValue('TagPanel.enableTagPanel'), }; BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate); + this.tagPanelWatcherRef = SettingsStore.watchSetting("TagPanel.enableTagPanel", null, () => { + this.setState({showTagPanel: SettingsStore.getValue("TagPanel.enableTagPanel")}); + }); // We watch the middle panel because we don't actually get resized, the middle panel does. // We listen to the noisy channel to avoid choppy reaction times. @@ -81,6 +88,7 @@ export default class LeftPanel2 extends React.Component { } public componentWillUnmount() { + SettingsStore.unwatchSetting(this.tagPanelWatcherRef); BreadcrumbsStore.instance.off(UPDATE_EVENT, this.onBreadcrumbsUpdate); this.props.resizeNotifier.off("middlePanelResizedNoisy", this.onResize); OwnProfileStore.instance.off(UPDATE_EVENT, this.onProfileUpdate); @@ -231,7 +239,7 @@ export default class LeftPanel2 extends React.Component { } public render(): React.ReactNode { - const tagPanel = ( + const tagPanel = !this.state.showTagPanel ? null : (
          @@ -252,6 +260,7 @@ export default class LeftPanel2 extends React.Component { const containerClasses = classNames({ "mx_LeftPanel2": true, + "mx_LeftPanel2_hasTagPanel": !!tagPanel, "mx_LeftPanel2_minimized": this.props.isMinimized, }); From b1b15248662026d79c5d6ca9678ddbfc0b9a77e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Fri, 26 Jun 2020 06:23:00 +0000 Subject: [PATCH 137/294] Translated using Weblate (French) Currently translated at 100.0% (2291 of 2291 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/fr/ --- src/i18n/strings/fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index e6762eee32..fa66048da0 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -2537,5 +2537,6 @@ "Modern": "Moderne", "Use a system font": "Utiliser une police du système", "System font name": "Nom de la police du système", - "The authenticity of this encrypted message can't be guaranteed on this device.": "L’authenticité de ce message chiffré ne peut pas être garantie sur cet appareil." + "The authenticity of this encrypted message can't be guaranteed on this device.": "L’authenticité de ce message chiffré ne peut pas être garantie sur cet appareil.", + "Use a more compact ‘Modern’ layout": "Utiliser une mise en page « moderne » plus compacte" } From 2aa791d20a75f3c2f7ebbc9380991574d6076a88 Mon Sep 17 00:00:00 2001 From: XoseM Date: Fri, 26 Jun 2020 07:07:23 +0000 Subject: [PATCH 138/294] Translated using Weblate (Galician) Currently translated at 98.1% (2247 of 2291 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/gl/ --- src/i18n/strings/gl.json | 103 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index 6844317dbe..e73d7e879e 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -2224,5 +2224,106 @@ "Syncing...": "Sincronizando...", "Signing In...": "Conectando con...", "If you've joined lots of rooms, this might take a while": "Se te uniches a moitas salas, esto podería levarnos un anaco", - "Create account": "Crea unha conta" + "Create account": "Crea unha conta", + "Use a more compact ‘Modern’ layout": "Usa o deseño compacto 'Moderno'", + "Unable to query for supported registration methods.": "Non se puido consultar os métodos de rexistro soportados.", + "Registration has been disabled on this homeserver.": "O rexistro está desactivado neste servidor.", + "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).": "A tú conta (%(newAccountId)s) foi rexistrada, pero estás conectada usando outra conta (%(loggedInUserId)s).", + "Continue with previous account": "Continúa coa conta anterior", + "Log in to your new account.": "Conecta usando a conta nova.", + "You can now close this window or log in to your new account.": "Podes pechar esta ventá ou conectar usando a conta nova.", + "Registration Successful": "Rexistro correcto", + "Create your account": "Crea a túa conta", + "Use Recovery Key or Passphrase": "Usa a Chave de recuperación ou Frase de paso", + "Use Recovery Key": "Usa chave de recuperación", + "Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.": "Confirma a túa identidade verificando esta conexión desde unha das outras sesións, permitindo así acceder ás mensaxes cifradas.", + "This requires the latest Riot on your other devices:": "Require a última versión de Riot nos outros dispositivos:", + "or another cross-signing capable Matrix client": "ou outro cliente Matrix que permita a sinatura-cruzada", + "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "A nova sesión foi verificada. Tes acceso ás mensaxes cifradas, e outras persoas verante como confiable.", + "Your new session is now verified. Other users will see it as trusted.": "A nova sesión foi verificada. Outras persoas verante como confiable.", + "Without completing security on this session, it won’t have access to encrypted messages.": "Sen non garantes a seguridade para esta sesión non poderá acceder a mensaxes cifradas.", + "Go Back": "Atrás", + "Failed to re-authenticate due to a homeserver problem": "Fallo ó reautenticar debido a un problema no servidor", + "Failed to re-authenticate": "Fallo na reautenticación", + "Regain access to your account and recover encryption keys stored in this session. Without them, you won’t be able to read all of your secure messages in any session.": "Recupera o acceso á túa conta e recupera as chaves de cifrado gardadas nesta sesión. Sen elas, non poderás ler as mensaxes seguras en calquera das sesións.", + "Forgotten your password?": "¿Esqueceches o contrasinal?", + "You're signed out": "Estás desconectada", + "Clear personal data": "Baleirar datos personais", + "Command Autocomplete": "Autocompletado de comandos", + "Community Autocomplete": "Autocompletado de comunidade", + "DuckDuckGo Results": "Resultados DuckDuckGo", + "Emoji Autocomplete": "Autocompletado emoticonas", + "Notification Autocomplete": "Autocompletado de notificacións", + "Room Autocomplete": "Autocompletado de Salas", + "User Autocomplete": "Autocompletados de Usuaria", + "Confirm encryption setup": "Confirma os axustes de cifrado", + "Click the button below to confirm setting up encryption.": "Preme no botón inferior para confirmar os axustes do cifrado.", + "Enter your account password to confirm the upgrade:": "Escribe o contrasinal para confirmar a actualización:", + "Restore your key backup to upgrade your encryption": "Restablece a copia das chaves para actualizar o cifrado", + "Restore": "Restablecer", + "You'll need to authenticate with the server to confirm the upgrade.": "Debes autenticarte no servidor para confirmar a actualización.", + "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Actualiza esta sesión para permitirlle que verifique as outras sesións, outorgándolles acceso ás mensaxes cifradas e marcándoas como confiables para outras usuarias.", + "Set a recovery passphrase to secure encrypted information and recover it if you log out. This should be different to your account password:": "Establece unha frase de paso de recuperación para asegurar a información cifrada e recuperala se te desconectas. Esta frase debería ser diferente ó contrasinal da conta:", + "Enter a recovery passphrase": "Escribe a frase de paso de recuperación", + "Great! This recovery passphrase looks strong enough.": "Ben! Esta frase de paso de recuperación semella ser forte.", + "Back up encrypted message keys": "Fai copia das chaves das mensaxes cifradas", + "Set up with a recovery key": "Configura cunha chave de recuperación", + "That matches!": "Concorda!", + "Use a different passphrase?": "¿Usar unha frase de paso diferente?", + "That doesn't match.": "Non concorda.", + "Go back to set it again.": "Vai atrás e volve a escribila.", + "Enter your recovery passphrase a second time to confirm it.": "Escribe a frase de paso de recuperación por segunda vez para confirmala.", + "Confirm your recovery passphrase": "Confirma a frase de paso de recuperación", + "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.": "A chave de recuperación é unha rede de seguridade - podes usala para recuperar o acceso ás mensaxes cifradas se esqueces a frase de paso de recuperación.", + "Keep a copy of it somewhere secure, like a password manager or even a safe.": "Garda unha copia nun lugar seguro, como un xestor de contrasinais ou nun lugar aínda máis seguro.", + "Your recovery key": "A chave de recuperación", + "Copy": "Copiar", + "Download": "Descargar", + "Your recovery key has been copied to your clipboard, paste it to:": "A chave de recuperación foi copiada no portapapeis, pégaa en:", + "Your recovery key is in your Downloads folder.": "A chave de recuperación está no teu cartafol de Descargas.", + "Print it and store it somewhere safe": "Imprímea e gárdaa nun lugar seguro", + "Save it on a USB key or backup drive": "Gárdaa nunha memoria USB ou disco duro", + "Copy it to your personal cloud storage": "Copiaa no almacenaxe personal na nube", + "Unable to query secret storage status": "Non se obtivo o estado do almacenaxe segredo", + "Retry": "Reintentar", + "You can now verify your other devices, and other users to keep your chats safe.": "Xa podes verificar os teus outros dispositivos e a outras usuarias para manter conversas seguras.", + "Upgrade your encryption": "Mellora o teu cifrado", + "Confirm recovery passphrase": "Confirma a frase de paso de recuperación", + "Make a copy of your recovery key": "Fai unha copia da túa chave de recuperación", + "You're done!": "Feito!", + "Unable to set up secret storage": "Non se configurou un almacenaxe segredo", + "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.": "Imos gardar unha copia cifrada das túas chaves no noso servidor. Asegura a copia cunha frase de paso de recuperación.", + "For maximum security, this should be different from your account password.": "Para máxima seguridade, esta debería ser diferente ó contrasinal da túa conta.", + "Please enter your recovery passphrase a second time to confirm.": "Escribe a frase de paso de recuperación outra vez para confirmala.", + "Repeat your recovery passphrase...": "Repite a frase de paso de recuperación...", + "Your keys are being backed up (the first backup could take a few minutes).": "As chaves estanse a copiar (a primeira copia podería tardar un anaco).", + "Without setting up Secure Message Recovery, you won't be able to restore your encrypted message history if you log out or use another session.": "Se non configuras Recuperación de Mensaxes Seguras, non poderás restablecer o historial de mensaxes cifradas se te desconectas ou usas outra sesión.", + "Set up Secure Message Recovery": "Cofigurar Recuperación de Mensaxes Seguras", + "Secure your backup with a recovery passphrase": "Asegura a túa copia cunha frase de paso de recuperación", + "Starting backup...": "Iniciando a copia...", + "Success!": "Feito!", + "Create key backup": "Crear copia da chave", + "Unable to create key backup": "Non se creou a copia da chave", + "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.": "Se non configuras a Recuperación de Mensaxes Seguras, perderás o acceso ó historial de mensaxes seguras cando te desconectes.", + "If you don't want to set this up now, you can later in Settings.": "Se non queres configurar esto agora, pódelo facer posteriormente nos Axustes.", + "Don't ask again": "Non preguntar outra vez", + "New Recovery Method": "Novo Método de Recuperación", + "A new recovery passphrase and key for Secure Messages have been detected.": "Detectouse un novo método de chave e frase de paso de recuperación para Mensaxes Seguras.", + "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Se non configuras o novo método de recuperación, un atacante podería intentar o acceso á túa conta. Cambia inmediatamente o contrasinal da conta e configura un novo método de recuperación nos Axustes.", + "This session is encrypting history using the new recovery method.": "Esta sesión está cifrando o historial usando o novo método de recuperación.", + "Go to Settings": "Ir a Axustes", + "Set up Secure Messages": "Configurar Mensaxes Seguras", + "Recovery Method Removed": "Método de Recuperación eliminado", + "This session has detected that your recovery passphrase and key for Secure Messages have been removed.": "Esta sesión detectou que a túa frase de paso de recuperación e chave para Mensaxes Seguras foron eliminadas.", + "If you did this accidentally, you can setup Secure Messages on this session which will re-encrypt this session's message history with a new recovery method.": "Se fixeches esto sen querer, podes configurar Mensaxes Seguras nesta sesión e volverá a cifrar as mensaxes da sesión cun novo método de recuperación.", + "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Se non eliminaches o método de recuperación, un atacante podería estar a intentar acceder á túa conta. Cambia inmediatamente o contrasinal da conta e establece un novo método de recuperación nos Axustes.", + "If disabled, messages from encrypted rooms won't appear in search results.": "Se está desactivado, as mensaxes das salas cifradas non aparecerán nos resultados das buscas.", + "Disable": "Desactivar", + "Not currently indexing messages for any room.": "Non se están indexando as mensaxes de ningunha sala.", + "Currently indexing: %(currentRoom)s": "Indexando actualmente: %(currentRoom)s", + "Riot is securely caching encrypted messages locally for them to appear in search results:": "Riot está gardando de xeito seguro na caché local mensaxes cifradas para que aparezan nos resultados das buscas:", + "Space used:": "Espazo utilizado:", + "Indexed messages:": "Mensaxes indexadas:", + "Indexed rooms:": "Salas indexadas:", + "%(doneRooms)s out of %(totalRooms)s": "%(doneRooms)s de %(totalRooms)s" } From b16344582a45e91dfe6979ddc3bb7d65853b4f31 Mon Sep 17 00:00:00 2001 From: aWeinzierl Date: Fri, 26 Jun 2020 01:35:15 +0000 Subject: [PATCH 139/294] Translated using Weblate (Spanish) Currently translated at 89.4% (2048 of 2291 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/es/ --- src/i18n/strings/es.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/es.json b/src/i18n/strings/es.json index 8004e5cd88..2b3cb7b1b9 100644 --- a/src/i18n/strings/es.json +++ b/src/i18n/strings/es.json @@ -1556,7 +1556,7 @@ "Sign In or Create Account": "Iniciar sesión o Crear una cuenta", "Use your account or create a new one to continue.": "Usa tu cuenta existente o crea una nueva para continuar.", "Create Account": "Crear cuenta", - "Sign In": "Registrarse", + "Sign In": "Iniciar sesión", "Sends a message as html, without interpreting it as markdown": "Envía un mensaje como html, sin interpretarlo en markdown", "Failed to set topic": "No se ha podido establecer el tema", "Command failed": "El comando falló", From 9391d151f363289b9860014c33a5508104a8edfb Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 26 Jun 2020 09:15:02 +0100 Subject: [PATCH 140/294] ts-ignore because something is made of fail Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/MatrixChat.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 7f838f1e9e..a205a4fb26 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1620,6 +1620,7 @@ export default class MatrixChat extends React.PureComponent { const {hsUrl, isUrl} = this.props.serverConfig; cli = createClient({ baseUrl: hsUrl, + // @ts-ignore - XXX: remove me when it doesn't break tests idBaseUrl: isUrl, }); } From 228a6adfdf17f4b3a8d8c81649f8f7c050280c63 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Fri, 26 Jun 2020 09:27:33 +0100 Subject: [PATCH 141/294] indentation --- src/components/views/elements/Spinner.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/views/elements/Spinner.js b/src/components/views/elements/Spinner.js index ee4351fed6..08ba0cf921 100644 --- a/src/components/views/elements/Spinner.js +++ b/src/components/views/elements/Spinner.js @@ -34,13 +34,13 @@ const Spinner = ({w = 32, h = 32, imgClassName, message}) => { return (
          { message &&
          { message}
           
          } - +
          ); }; From 274e6f3825cdd0ffcaf67d34bff44f10c2730e15 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 26 Jun 2020 09:35:29 +0100 Subject: [PATCH 142/294] make js-sdk import happy? Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/MatrixChat.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index a205a4fb26..9ee8a50c50 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -18,7 +18,7 @@ limitations under the License. */ import React, { createRef } from 'react'; -import { createClient } from "matrix-js-sdk/src"; +import * as Matrix from "matrix-js-sdk"; import { InvalidStoreError } from "matrix-js-sdk/src/errors"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; @@ -1618,9 +1618,8 @@ export default class MatrixChat extends React.PureComponent { let cli = MatrixClientPeg.get(); if (!cli) { const {hsUrl, isUrl} = this.props.serverConfig; - cli = createClient({ + cli = Matrix.createClient({ baseUrl: hsUrl, - // @ts-ignore - XXX: remove me when it doesn't break tests idBaseUrl: isUrl, }); } From a905028d3a12c241004cb290d569a044feb1e52b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 26 Jun 2020 09:37:55 +0100 Subject: [PATCH 143/294] bandaid Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/MatrixChat.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 9ee8a50c50..7da565739e 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -18,6 +18,7 @@ limitations under the License. */ import React, { createRef } from 'react'; +// @ts-ignore - XXX: no idea why this import fails import * as Matrix from "matrix-js-sdk"; import { InvalidStoreError } from "matrix-js-sdk/src/errors"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; From a2e33a23861da421c21bd5e51eab8d501038a416 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Fri, 26 Jun 2020 09:41:18 +0100 Subject: [PATCH 144/294] Prevent old InlineSpinner gif from spinning --- res/css/views/elements/_InlineSpinner.scss | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/res/css/views/elements/_InlineSpinner.scss b/res/css/views/elements/_InlineSpinner.scss index 561b6cfb82..bdd879b23d 100644 --- a/res/css/views/elements/_InlineSpinner.scss +++ b/res/css/views/elements/_InlineSpinner.scss @@ -18,15 +18,7 @@ limitations under the License. display: inline; } -.mx_InlineSpinner img { +.mx_InlineSpinner_spin img { margin: 0px 6px; vertical-align: -3px; - - animation: spin 1s linear infinite; -} - -@keyframes spin { - 100% { - transform: rotate(360deg); - } -} +} \ No newline at end of file From 96e4b938b2c65d2a87cae1da2c74fa337ce3e47f Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Fri, 26 Jun 2020 09:42:44 +0100 Subject: [PATCH 145/294] Don't modify the size of the MessagePanel spinner --- src/components/structures/MessagePanel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 481741dfd2..d11fee6360 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -770,7 +770,7 @@ export default class MessagePanel extends React.Component { topSpinner =
        • ; } if (this.props.forwardPaginating) { - bottomSpinner =
        • ; + bottomSpinner =
        • ; } const style = this.props.hidden ? { display: 'none' } : {}; From e790a31f09e09c9c3046416ff72cd06e8829aa30 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Fri, 26 Jun 2020 09:44:47 +0100 Subject: [PATCH 146/294] Include newline at end of _InlineSpinner.scss --- res/css/views/elements/_InlineSpinner.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/elements/_InlineSpinner.scss b/res/css/views/elements/_InlineSpinner.scss index bdd879b23d..6b91e45923 100644 --- a/res/css/views/elements/_InlineSpinner.scss +++ b/res/css/views/elements/_InlineSpinner.scss @@ -21,4 +21,4 @@ limitations under the License. .mx_InlineSpinner_spin img { margin: 0px 6px; vertical-align: -3px; -} \ No newline at end of file +} From bf45cb05880f0da6f56ba08cf43bb026dfffd0f3 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 26 Jun 2020 11:24:07 +0100 Subject: [PATCH 147/294] PR feedback: re-order CSS & add underscore --- .../views/elements/_StyledRadioButton.scss | 3 +-- .../CreateSecretStorageDialog.js | 20 +++++++++---------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/res/css/views/elements/_StyledRadioButton.scss b/res/css/views/elements/_StyledRadioButton.scss index eb73cec5b5..17a063593f 100644 --- a/res/css/views/elements/_StyledRadioButton.scss +++ b/res/css/views/elements/_StyledRadioButton.scss @@ -35,11 +35,10 @@ limitations under the License. flex-grow: 1; display: flex; + flex-direction: column; margin-left: 8px; margin-right: 8px; - - flex-direction: column; } .mx_RadioButton_spacer { diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index b4b99f2205..bd751f7e74 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -44,8 +44,8 @@ const PHASE_CONFIRM_SKIP = 10; const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc. // these end up as strings from being values in the radio buttons, so just use strings -const CREATESTORAGE_OPTION_KEY = 'key'; -const CREATESTORAGE_OPTION_PASSPHRASE = 'passphrase'; +const CREATE_STORAGE_OPTION_KEY = 'key'; +const CREATE_STORAGE_OPTION_PASSPHRASE = 'passphrase'; /* * Walks the user through the process of creating a passphrase to guard Secure @@ -86,7 +86,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { accountPassword: props.accountPassword || "", accountPasswordCorrect: null, - passPhraseKeySelected: CREATESTORAGE_OPTION_KEY, + passPhraseKeySelected: CREATE_STORAGE_OPTION_KEY, }; this._passphraseField = createRef(); @@ -171,7 +171,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { } _onChooseKeyPassphraseFormSubmit = async () => { - if (this.state.passPhraseKeySelected === CREATESTORAGE_OPTION_KEY) { + if (this.state.passPhraseKeySelected === CREATE_STORAGE_OPTION_KEY) { this._recoveryKey = await MatrixClientPeg.get().createRecoveryKeyFromPassphrase(); this.setState({ @@ -440,10 +440,10 @@ export default class CreateSecretStorageDialog extends React.PureComponent { )}

          {_t("We’ll generate a Security Key for you to store somewhere safe, like a password manager or a safe.")}
          Date: Fri, 26 Jun 2020 12:41:24 +0100 Subject: [PATCH 148/294] Convert icons to masks so they're a sensible colour in other themes --- .../_CreateSecretStorageDialog.scss | 30 +++++++++++++++++++ .../CreateSecretStorageDialog.js | 23 +++++--------- src/components/views/dialogs/BaseDialog.js | 8 +++-- 3 files changed, 44 insertions(+), 17 deletions(-) diff --git a/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss b/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss index c591973c94..b073bac93c 100644 --- a/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss +++ b/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss @@ -48,6 +48,25 @@ limitations under the License. margin-bottom: 1em; } +.mx_CreateSecretStorageDialog_titleWithIcon::before { + content: ''; + display: inline-block; + width: 24px; + height: 24px; + margin-right: 8px; + position: relative; + top: 5px; + background-color: $primary-fg-color; +} + +.mx_CreateSecretStorageDialog_secureBackupTitle::before { + mask-image: url('$(res)/img/feather-customised/secure-backup.svg'); +} + +.mx_CreateSecretStorageDialog_securePhraseTitle::before { + mask-image: url('$(res)/img/feather-customised/secure-phrase.svg'); +} + .mx_CreateSecretStorageDialog_centeredTitle, .mx_CreateSecretStorageDialog_centeredBody { text-align: center; } @@ -75,10 +94,21 @@ limitations under the License. } .mx_CreateSecretStorageDialog_optionIcon { + display: inline-block; width: 24px; + height: 24px; margin-right: 8px; position: relative; top: 5px; + background-color: $primary-fg-color; +} + +.mx_CreateSecretStorageDialog_optionIcon_securePhrase { + mask-image: url('$(res)/img/feather-customised/secure-phrase.svg'); +} + +.mx_CreateSecretStorageDialog_optionIcon_secureBackup { + mask-image: url('$(res)/img/feather-customised/secure-backup.svg'); } .mx_CreateSecretStorageDialog_passPhraseContainer { diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index bd751f7e74..ebef30ad1f 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -446,9 +446,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { checked={this.state.passPhraseKeySelected === CREATE_STORAGE_OPTION_KEY} >
          - + {_t("Generate a Security Key")}
          {_t("We’ll generate a Security Key for you to store somewhere safe, like a password manager or a safe.")}
          @@ -460,9 +458,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { checked={this.state.passPhraseKeySelected === CREATE_STORAGE_OPTION_PASSPHRASE} >
          - + {_t("Enter a Security Phrase")}
          {_t("Use a secret phrase only you know, and optionally save a Security Key to use for backup.")}
          @@ -781,20 +777,18 @@ export default class CreateSecretStorageDialog extends React.PureComponent { } } - let headerImage = null; + let titleClass = null; switch (this.state.phase) { case PHASE_PASSPHRASE: case PHASE_PASSPHRASE_CONFIRM: - headerImage = require("../../../../../res/img/feather-customised/secure-phrase.svg"); + titleClass = ['mx_CreateSecretStorageDialog_titleWithIcon', 'mx_CreateSecretStorageDialog_securePhraseTitle']; break; case PHASE_SHOWKEY: - headerImage = require("../../../../../res/img/feather-customised/secure-backup.svg"); + titleClass = ['mx_CreateSecretStorageDialog_titleWithIcon', 'mx_CreateSecretStorageDialog_secureBackupTitle']; + break; + case PHASE_CHOOSE_KEY_PASSPHRASE: + titleClass = 'mx_CreateSecretStorageDialog_centeredTitle'; break; - } - - let titleClass = null; - if (this.state.phase === PHASE_CHOOSE_KEY_PASSPHRASE) { - titleClass = 'mx_CreateSecretStorageDialog_centeredTitle'; } return ( @@ -802,7 +796,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent { onFinished={this.props.onFinished} title={this._titleForPhase(this.state.phase)} titleClass={titleClass} - headerImage={headerImage} hasCancel={this.props.hasCancel && [PHASE_PASSPHRASE].includes(this.state.phase)} fixedWidth={false} > diff --git a/src/components/views/dialogs/BaseDialog.js b/src/components/views/dialogs/BaseDialog.js index e59b6bbaf5..353298032c 100644 --- a/src/components/views/dialogs/BaseDialog.js +++ b/src/components/views/dialogs/BaseDialog.js @@ -75,8 +75,12 @@ export default createReactClass({ // If provided, this is used to add a aria-describedby attribute contentId: PropTypes.string, - // optional additional class for the title element - titleClass: PropTypes.string, + // optional additional class for the title element (basically anything that can be passed to classnames) + titleClass: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.object, + PropTypes.arrayOf(PropTypes.string), + ]), }, getDefaultProps: function() { From 919c3bd360b37f61221cf299672a4e956e567c6d Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 26 Jun 2020 12:43:28 +0100 Subject: [PATCH 149/294] lint --- .../dialogs/secretstorage/CreateSecretStorageDialog.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index ebef30ad1f..58b5b57354 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -781,10 +781,16 @@ export default class CreateSecretStorageDialog extends React.PureComponent { switch (this.state.phase) { case PHASE_PASSPHRASE: case PHASE_PASSPHRASE_CONFIRM: - titleClass = ['mx_CreateSecretStorageDialog_titleWithIcon', 'mx_CreateSecretStorageDialog_securePhraseTitle']; + titleClass = [ + 'mx_CreateSecretStorageDialog_titleWithIcon', + 'mx_CreateSecretStorageDialog_securePhraseTitle', + ]; break; case PHASE_SHOWKEY: - titleClass = ['mx_CreateSecretStorageDialog_titleWithIcon', 'mx_CreateSecretStorageDialog_secureBackupTitle']; + titleClass = [ + 'mx_CreateSecretStorageDialog_titleWithIcon', + 'mx_CreateSecretStorageDialog_secureBackupTitle', + ]; break; case PHASE_CHOOSE_KEY_PASSPHRASE: titleClass = 'mx_CreateSecretStorageDialog_centeredTitle'; From 178cbca934198ad5f37c440786a7970be9bda8db Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 26 Jun 2020 12:56:41 +0100 Subject: [PATCH 150/294] Use mask images in key entry dialogs --- .../_AccessSecretStorageDialog.scss | 19 +++++++++++++++++++ .../AccessSecretStorageDialog.js | 8 ++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/res/css/views/dialogs/secretstorage/_AccessSecretStorageDialog.scss b/res/css/views/dialogs/secretstorage/_AccessSecretStorageDialog.scss index 785eb85374..f15d43b199 100644 --- a/res/css/views/dialogs/secretstorage/_AccessSecretStorageDialog.scss +++ b/res/css/views/dialogs/secretstorage/_AccessSecretStorageDialog.scss @@ -15,6 +15,25 @@ See the License for the specific language governing permissions and limitations under the License. */ +.mx_AccessSecretStorageDialog_titleWithIcon::before { + content: ''; + display: inline-block; + width: 24px; + height: 24px; + margin-right: 8px; + position: relative; + top: 5px; + background-color: $primary-fg-color; +} + +.mx_AccessSecretStorageDialog_secureBackupTitle::before { + mask-image: url('$(res)/img/feather-customised/secure-backup.svg'); +} + +.mx_AccessSecretStorageDialog_securePhraseTitle::before { + mask-image: url('$(res)/img/feather-customised/secure-phrase.svg'); +} + .mx_AccessSecretStorageDialog_keyStatus { height: 30px; } diff --git a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js index b76c62323b..bb9937c429 100644 --- a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js +++ b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js @@ -111,12 +111,12 @@ export default class AccessSecretStorageDialog extends React.PureComponent { let content; let title; - let headerImage; + let titleClass; if (hasPassphrase && !this.state.forceRecoveryKey) { const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); title = _t("Security Phrase"); - headerImage = require("../../../../../res/img/feather-customised/secure-phrase.svg"); + titleClass = ['mx_AccessSecretStorageDialog_titleWithIcon mx_AccessSecretStorageDialog_securePhraseTitle']; let keyStatus; if (this.state.keyMatches === false) { @@ -166,7 +166,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
          ; } else { title = _t("Security Key"); - headerImage = require("../../../../../res/img/feather-customised/secure-backup.svg"); + titleClass = ['mx_AccessSecretStorageDialog_titleWithIcon mx_AccessSecretStorageDialog_secureBackupTitle']; const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); let keyStatus; @@ -213,9 +213,9 @@ export default class AccessSecretStorageDialog extends React.PureComponent { return (
          {content} From 46058a17f8718743db3cc29fdde7cea1615ad4ed Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 26 Jun 2020 14:18:38 +0100 Subject: [PATCH 151/294] Fix Room Custom Sounds regression Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../views/settings/tabs/room/NotificationSettingsTab.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/settings/tabs/room/NotificationSettingsTab.js b/src/components/views/settings/tabs/room/NotificationSettingsTab.js index c521e228e0..257f4a5d23 100644 --- a/src/components/views/settings/tabs/room/NotificationSettingsTab.js +++ b/src/components/views/settings/tabs/room/NotificationSettingsTab.js @@ -28,6 +28,8 @@ export default class NotificationsSettingsTab extends React.Component { roomId: PropTypes.string.isRequired, }; + _soundUpload = createRef(); + constructor() { super(); @@ -44,8 +46,6 @@ export default class NotificationsSettingsTab extends React.Component { return; } this.setState({currentSound: soundData.name || soundData.url}); - - this._soundUpload = createRef(); } async _triggerUploader(e) { From 72035c807804b3741d8a8372b6b6c857bf5b8081 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 26 Jun 2020 14:02:36 +0100 Subject: [PATCH 152/294] Make relevant again Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/_common.scss | 21 ++++++++++ res/css/views/auth/_PassphraseField.scss | 19 +-------- res/css/views/elements/_ProgressBar.scss | 30 ++++++++++---- src/components/views/elements/ProgressBar.js | 39 ------------------- src/components/views/elements/ProgressBar.tsx | 28 +++++++++++++ 5 files changed, 72 insertions(+), 65 deletions(-) delete mode 100644 src/components/views/elements/ProgressBar.js create mode 100644 src/components/views/elements/ProgressBar.tsx diff --git a/res/css/_common.scss b/res/css/_common.scss index e83c6aaeda..e079292bdc 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -696,3 +696,24 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { } } } + +@define-mixin ProgressBarColour $colour { + color: $colour; + &::-moz-progress-bar { + background-color: $colour; + } + &::-webkit-progress-value { + background-color: $colour; + } +} + +@define-mixin ProgressBarBorderRadius $radius { + border-radius: $radius; + &::-moz-progress-bar { + border-radius: $radius; + } + &::-webkit-progress-bar, + &::-webkit-progress-value { + border-radius: $radius; + } +} diff --git a/res/css/views/auth/_PassphraseField.scss b/res/css/views/auth/_PassphraseField.scss index d1b8c47d00..2354c597ae 100644 --- a/res/css/views/auth/_PassphraseField.scss +++ b/res/css/views/auth/_PassphraseField.scss @@ -18,16 +18,6 @@ $PassphraseStrengthHigh: $accent-color; $PassphraseStrengthMedium: $username-variant5-color; $PassphraseStrengthLow: $notice-primary-color; -@define-mixin ProgressBarColour $colour { - color: $colour; - &::-moz-progress-bar { - background-color: $colour; - } - &::-webkit-progress-value { - background-color: $colour; - } -} - progress.mx_PassphraseField_progress { appearance: none; width: 100%; @@ -36,14 +26,7 @@ progress.mx_PassphraseField_progress { position: absolute; top: -12px; - border-radius: 2px; - &::-moz-progress-bar { - border-radius: 2px; - } - &::-webkit-progress-bar, - &::-webkit-progress-value { - border-radius: 2px; - } + @mixin ProgressBarBorderRadius "2px"; @mixin ProgressBarColour $PassphraseStrengthLow; &[value="2"], &[value="3"] { diff --git a/res/css/views/elements/_ProgressBar.scss b/res/css/views/elements/_ProgressBar.scss index a3fee232d0..e49d85af04 100644 --- a/res/css/views/elements/_ProgressBar.scss +++ b/res/css/views/elements/_ProgressBar.scss @@ -1,5 +1,5 @@ /* -Copyright 2015, 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,12 +14,26 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_ProgressBar { - height: 5px; - border: 1px solid $progressbar-color; -} +progress.mx_ProgressBar { + height: 4px; + width: 60px; + border-radius: 10px; + overflow: hidden; + appearance: none; + border: 0; -.mx_ProgressBar_fill { - height: 100%; - background-color: $progressbar-color; + @mixin ProgressBarBorderRadius "10px"; + @mixin ProgressBarColour $accent-color; + ::-webkit-progress-value { + transition: width 1s; + } + ::-moz-progress-bar { + transition: padding-bottom 1s; + padding-bottom: var(--value); + transform-origin: 0 0; + transform: rotate(-90deg) translateX(-15px); + padding-left: 15px; + + height: 0; + } } diff --git a/src/components/views/elements/ProgressBar.js b/src/components/views/elements/ProgressBar.js deleted file mode 100644 index 045731ba38..0000000000 --- a/src/components/views/elements/ProgressBar.js +++ /dev/null @@ -1,39 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2019 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. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from "react"; -import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; - -export default createReactClass({ - displayName: 'ProgressBar', - propTypes: { - value: PropTypes.number, - max: PropTypes.number, - }, - - render: function() { - // Would use an HTML5 progress tag but if that doesn't animate if you - // use the HTML attributes rather than styles - const progressStyle = { - width: ((this.props.value / this.props.max) * 100)+"%", - }; - return ( -
          - ); - }, -}); diff --git a/src/components/views/elements/ProgressBar.tsx b/src/components/views/elements/ProgressBar.tsx new file mode 100644 index 0000000000..90832e5006 --- /dev/null +++ b/src/components/views/elements/ProgressBar.tsx @@ -0,0 +1,28 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from "react"; + +interface IProps { + value: number; + max: number; +} + +const ProgressBar: React.FC = ({value, max}) => { + return ; +}; + +export default ProgressBar; From f830a4b7fcb8f3cf19b31a0b5a84901d3303efd6 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 26 Jun 2020 14:22:59 +0100 Subject: [PATCH 153/294] delint Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/views/auth/_PassphraseField.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/res/css/views/auth/_PassphraseField.scss b/res/css/views/auth/_PassphraseField.scss index 2354c597ae..bf8e7f4438 100644 --- a/res/css/views/auth/_PassphraseField.scss +++ b/res/css/views/auth/_PassphraseField.scss @@ -27,7 +27,6 @@ progress.mx_PassphraseField_progress { top: -12px; @mixin ProgressBarBorderRadius "2px"; - @mixin ProgressBarColour $PassphraseStrengthLow; &[value="2"], &[value="3"] { @mixin ProgressBarColour $PassphraseStrengthMedium; From c0ad857e5ca44d3f43ddba358a2e5ea480f29178 Mon Sep 17 00:00:00 2001 From: XoseM Date: Fri, 26 Jun 2020 11:39:48 +0000 Subject: [PATCH 154/294] Translated using Weblate (Galician) Currently translated at 100.0% (2292 of 2292 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/gl/ --- src/i18n/strings/gl.json | 47 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index e73d7e879e..d88c3e95c6 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -2325,5 +2325,50 @@ "Space used:": "Espazo utilizado:", "Indexed messages:": "Mensaxes indexadas:", "Indexed rooms:": "Salas indexadas:", - "%(doneRooms)s out of %(totalRooms)s": "%(doneRooms)s de %(totalRooms)s" + "%(doneRooms)s out of %(totalRooms)s": "%(doneRooms)s de %(totalRooms)s", + "New spinner design": "Novo deseño da roda", + "Message downloading sleep time(ms)": "Tempo de espera da mensaxe de descarga(ms)", + "Navigation": "Navegación", + "Calls": "Chamadas", + "Room List": "Lista de Salas", + "Autocomplete": "Autocompletado", + "Alt": "Alt", + "Alt Gr": "Alt Gr", + "Shift": "Maiús.", + "Super": "Super", + "Ctrl": "Ctrl", + "Toggle Bold": "Activa Resaltar", + "Toggle Italics": "Activa Cursiva", + "Toggle Quote": "Activa Citación", + "New line": "Nova liña", + "Navigate recent messages to edit": "Mira nas mensaxes recentes para editar", + "Jump to start/end of the composer": "Vai ó inicio/fin no editor", + "Navigate composer history": "Vai ó historial do editor", + "Cancel replying to a message": "Cancelar a resposta a mensaxe", + "Toggle microphone mute": "Acalar micrófono", + "Toggle video on/off": "Activar vídeo on/off", + "Scroll up/down in the timeline": "Desprazarse arriba/abaixo na cronoloxía", + "Dismiss read marker and jump to bottom": "Ignorar marcador de lectura e ir ó final", + "Jump to oldest unread message": "Ir á mensaxe máis antiga non lida", + "Upload a file": "Subir ficheiro", + "Jump to room search": "Ir a busca na sala", + "Navigate up/down in the room list": "Ir arriba/abaixo na lista de salas", + "Select room from the room list": "Escoller sala da lista de salas", + "Collapse room list section": "Contraer a sección de lista de salas", + "Expand room list section": "Expandir a sección da lista de salas", + "Previous/next unread room or DM": "Anterior/seguinte para salas non lidas ou MD", + "Previous/next room or DM": "Anterior/seguinte para sala ou MD", + "Toggle the top left menu": "Activar o menú superior esquerdo", + "Close dialog or context menu": "Pechar o diálogo ou menú contextual", + "Activate selected button": "Activar o botón seleccionado", + "Toggle right panel": "Activar panel dereito", + "Toggle this dialog": "Activar este diálogo", + "Move autocomplete selection up/down": "Mover selección autocompletado arriba/abaixo", + "Cancel autocomplete": "Cancelar autocompletado", + "Page Up": "Páxina superior", + "Page Down": "Páxina inferior", + "Esc": "Esc", + "Enter": "Intro", + "Space": "Espazo", + "End": "Fin" } From 15ebaa14704c08c5a8875a33ffac96109b14efaf Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 26 Jun 2020 15:22:04 +0100 Subject: [PATCH 155/294] Port recovery key upload button to new designs --- .../_AccessSecretStorageDialog.scss | 49 ++++- .../AccessSecretStorageDialog.js | 172 +++++++++++++++--- src/i18n/strings/en_EN.json | 10 +- 3 files changed, 198 insertions(+), 33 deletions(-) diff --git a/res/css/views/dialogs/secretstorage/_AccessSecretStorageDialog.scss b/res/css/views/dialogs/secretstorage/_AccessSecretStorageDialog.scss index f15d43b199..63d0ca555d 100644 --- a/res/css/views/dialogs/secretstorage/_AccessSecretStorageDialog.scss +++ b/res/css/views/dialogs/secretstorage/_AccessSecretStorageDialog.scss @@ -38,11 +38,56 @@ limitations under the License. height: 30px; } -.mx_AccessSecretStorageDialog_passPhraseInput, -.mx_AccessSecretStorageDialog_recoveryKeyInput { +.mx_AccessSecretStorageDialog_passPhraseInput { width: 300px; border: 1px solid $accent-color; border-radius: 5px; padding: 10px; } +.mx_AccessSecretStorageDialog_recoveryKeyEntry { + display: flex; + align-items: center; +} + +.mx_AccessSecretStorageDialog_recoveryKeyEntry_textInput { + flex-grow: 1; +} + +.mx_AccessSecretStorageDialog_recoveryKeyEntry_entryControlSeparatorText { + margin: 16px; +} + +.mx_AccessSecretStorageDialog_recoveryKeyFeedback { + &::before { + content: ""; + display: inline-block; + vertical-align: bottom; + width: 20px; + height: 20px; + mask-repeat: no-repeat; + mask-position: center; + mask-size: 20px; + margin-right: 5px; + } +} + +.mx_AccessSecretStorageDialog_recoveryKeyFeedback_valid { + color: $input-valid-border-color; + &::before { + mask-image: url('$(res)/img/feather-customised/check.svg'); + background-color: $input-valid-border-color; + } +} + +.mx_AccessSecretStorageDialog_recoveryKeyFeedback_invalid { + color: $input-invalid-border-color; + &::before { + mask-image: url('$(res)/img/feather-customised/x.svg'); + background-color: $input-invalid-border-color; + } +} + +.mx_AccessSecretStorageDialog_recoveryKeyEntry_fileInput { + display: none; +} diff --git a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js index bb9937c429..7713f07115 100644 --- a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js +++ b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js @@ -15,13 +15,26 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { debounce } from 'lodash'; +import classNames from 'classnames'; import React from 'react'; import PropTypes from "prop-types"; import * as sdk from '../../../../index'; import {MatrixClientPeg} from '../../../../MatrixClientPeg'; +import Field from '../../elements/Field'; +import AccessibleButton from '../../elements/AccessibleButton'; +import { decodeRecoveryKey } from 'matrix-js-sdk/src/crypto/recoverykey'; import { _t } from '../../../../languageHandler'; +// Maximum acceptable size of a key file. It's 59 characters including the spaces we encode, +// so this should be plenty and allow for people putting extra whitespace in the file because +// maybe that's a thing people would do? +const KEY_FILE_MAX_SIZE = 128; + +// Don't shout at the user that their key is invalid every time they type a key: wait a short time +const VALIDATION_THROTTLE_MS = 200; + /* * Access Secure Secret Storage by requesting the user's passphrase. */ @@ -35,9 +48,14 @@ export default class AccessSecretStorageDialog extends React.PureComponent { constructor(props) { super(props); + + this._fileUpload = React.createRef(); + this.state = { recoveryKey: "", - recoveryKeyValid: false, + recoveryKeyValid: null, + recoveryKeyCorrect: null, + recoveryKeyFileError: null, forceRecoveryKey: false, passPhrase: '', keyMatches: null, @@ -54,12 +72,89 @@ export default class AccessSecretStorageDialog extends React.PureComponent { }); } + _validateRecoveryKeyOnChange = debounce(() => { + this._validateRecoveryKey(); + }, VALIDATION_THROTTLE_MS); + + async _validateRecoveryKey() { + if (this.state.recoveryKey === '') { + this.setState({ + recoveryKeyValid: null, + recoveryKeyCorrect: null, + }); + return; + } + + try { + const decodedKey = decodeRecoveryKey(this.state.recoveryKey); + const correct = await MatrixClientPeg.get().checkSecretStorageKey( + decodedKey, this.props.keyInfo, + ); + this.setState({ + recoveryKeyValid: true, + recoveryKeyCorrect: correct, + }); + } catch (e) { + this.setState({ + recoveryKeyValid: false, + recoveryKeyCorrect: false, + }); + } + } + _onRecoveryKeyChange = (e) => { this.setState({ recoveryKey: e.target.value, - recoveryKeyValid: MatrixClientPeg.get().isValidRecoveryKey(e.target.value), - keyMatches: null, + recoveryKeyFileError: null, }); + + // also clear the file upload control so that the user can upload the same file + // the did before (otherwise the onchange wouldn't fire) + this._fileUpload.current.value = null; + + + // We don't use Field's validation here because a) we want it in a separate place rather + // than in a tooltip and b) we want it to display feedback based on the uploaded file + // as well as the text box. Ideally we would refactor Field's validation logic so we could + // re-use some of it. + this._validateRecoveryKeyOnChange(); + } + + _onRecoveryKeyFileChange = async e => { + if (e.target.files.length === 0) return; + + const f = e.target.files[0]; + + if (f.size > KEY_FILE_MAX_SIZE) { + this.setState({ + recoveryKeyFileError: true, + recoveryKeyCorrect: false, + recoveryKeyValid: false, + }); + } else { + const contents = await f.text(); + // test it's within the base58 alphabet. We could be more strict here, eg. require the + // right number of characters, but it's really just to make sure that what we're reading is + // text because we'll put it in the text field. + if (/^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\s]+$/.test(contents)) { + this.setState({ + recoveryKeyFileError: null, + recoveryKey: contents.trim(), + }); + this._validateRecoveryKey(); + } else { + this.setState({ + recoveryKeyFileError: true, + recoveryKeyCorrect: false, + recoveryKeyValid: false, + recoveryKey: '', + }); + } + } + } + + _onRecoveryKeyFileUploadClick = () => { + this._fileUpload.current.click(); } _onPassPhraseNext = async (e) => { @@ -99,6 +194,20 @@ export default class AccessSecretStorageDialog extends React.PureComponent { }); } + getKeyValidationText() { + if (this.state.recoveryKeyFileError) { + return _t("Wrong file type"); + } else if (this.state.recoveryKeyCorrect) { + return _t("Looks good!"); + } else if (this.state.recoveryKeyValid) { + return _t("Wrong Recovery Key"); + } else if (this.state.recoveryKeyValid === null) { + return ''; + } else { + return _t("Invalid Recovery Key"); + } + } + render() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); @@ -169,36 +278,43 @@ export default class AccessSecretStorageDialog extends React.PureComponent { titleClass = ['mx_AccessSecretStorageDialog_titleWithIcon mx_AccessSecretStorageDialog_secureBackupTitle']; const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); - let keyStatus; - if (this.state.recoveryKey.length === 0) { - keyStatus =
          ; - } else if (this.state.keyMatches === false) { - keyStatus =
          - {"\uD83D\uDC4E "}{_t( - "Unable to access secret storage. " + - "Please verify that you entered the correct recovery key.", - )} -
          ; - } else if (this.state.recoveryKeyValid) { - keyStatus =
          - {"\uD83D\uDC4D "}{_t("This looks like a valid recovery key!")} -
          ; - } else { - keyStatus =
          - {"\uD83D\uDC4E "}{_t("Not a valid recovery key")} -
          ; - } + const feedbackClasses = classNames({ + 'mx_AccessSecretStorageDialog_recoveryKeyFeedback': true, + 'mx_AccessSecretStorageDialog_recoveryKeyFeedback_valid': this.state.recoveryKeyCorrect === true, + 'mx_AccessSecretStorageDialog_recoveryKeyFeedback_invalid': this.state.recoveryKeyCorrect === false, + }); + const recoveryKeyFeedback =
          + {this.getKeyValidationText()} +
          ; content =

          {_t("Use your Security Key to continue.")}

          - - {keyStatus} +
          +
          + +
          + + {_t("or")} + +
          + + + {_t("Upload")} + +
          +
          + {recoveryKeyFeedback} Use your Security Key to continue.": "Enter your Security Phrase or to continue.", "Security Key": "Security Key", - "Unable to access secret storage. Please verify that you entered the correct recovery key.": "Unable to access secret storage. Please verify that you entered the correct recovery key.", - "This looks like a valid recovery key!": "This looks like a valid recovery key!", - "Not a valid recovery key": "Not a valid recovery key", "Use your Security Key to continue.": "Use your Security Key to continue.", + "Recovery Key": "Recovery Key", "Restoring keys from backup": "Restoring keys from backup", "Fetching keys from server...": "Fetching keys from server...", "%(completed)s of %(total)s keys restored": "%(completed)s of %(total)s keys restored", @@ -1813,6 +1815,8 @@ "Access your secure message history and set up secure messaging by entering your recovery passphrase.": "Access your secure message history and set up secure messaging by entering your recovery passphrase.", "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options": "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options", "Enter recovery key": "Enter recovery key", + "This looks like a valid recovery key!": "This looks like a valid recovery key!", + "Not a valid recovery key": "Not a valid recovery key", "Warning: You should only set up key backup from a trusted computer.": "Warning: You should only set up key backup from a trusted computer.", "Access your secure message history and set up secure messaging by entering your recovery key.": "Access your secure message history and set up secure messaging by entering your recovery key.", "If you've forgotten your recovery key you can ": "If you've forgotten your recovery key you can ", From b74674ced8b9a947fdb089fb471a5fb2c865cf06 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 26 Jun 2020 18:04:06 +0100 Subject: [PATCH 156/294] Right name for security key and fix cancel button --- .../views/dialogs/secretstorage/AccessSecretStorageDialog.js | 4 +++- src/i18n/strings/en_EN.json | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js index 7713f07115..3141cfb33b 100644 --- a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js +++ b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js @@ -295,7 +295,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
          @@ -319,6 +319,8 @@ export default class AccessSecretStorageDialog extends React.PureComponent { primaryButton={_t('Continue')} onPrimaryButtonClick={this._onRecoveryKeyNext} hasCancel={true} + cancelButton={_t("Go Back")} + cancelButtonClass='danger' onCancel={this._onCancel} focus={false} primaryDisabled={!this.state.recoveryKeyValid} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 7a2fb12867..9160d48f48 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1796,7 +1796,6 @@ "Enter your Security Phrase or to continue.": "Enter your Security Phrase or to continue.", "Security Key": "Security Key", "Use your Security Key to continue.": "Use your Security Key to continue.", - "Recovery Key": "Recovery Key", "Restoring keys from backup": "Restoring keys from backup", "Fetching keys from server...": "Fetching keys from server...", "%(completed)s of %(total)s keys restored": "%(completed)s of %(total)s keys restored", From 24baf19d6528cae719cedde35d3588b178f15421 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 26 Jun 2020 18:50:05 +0100 Subject: [PATCH 157/294] Set field validity (ie. border colour) correctly Changes flagInvalid to forceValidity which can force valid as well as invalid. --- .../dialogs/secretstorage/CreateSecretStorageDialog.js | 2 +- .../dialogs/secretstorage/AccessSecretStorageDialog.js | 1 + src/components/views/elements/Field.tsx | 10 +++++----- src/components/views/settings/SetIdServer.js | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index 58b5b57354..984158c7a2 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -491,7 +491,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { label={_t("Password")} value={this.state.accountPassword} onChange={this._onAccountPasswordChange} - flagInvalid={this.state.accountPasswordCorrect === false} + forceValidity={this.state.accountPasswordCorrect === false ? false : null} autoFocus={true} />
          ; diff --git a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js index 3141cfb33b..5029856f26 100644 --- a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js +++ b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js @@ -298,6 +298,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent { label={_t('Security Key')} value={this.state.recoveryKey} onChange={this._onRecoveryKeyChange} + forceValidity={this.state.recoveryKeyCorrect} />
          diff --git a/src/components/views/elements/Field.tsx b/src/components/views/elements/Field.tsx index 834edff7df..9a889a0351 100644 --- a/src/components/views/elements/Field.tsx +++ b/src/components/views/elements/Field.tsx @@ -50,7 +50,7 @@ interface IProps { // to the user. onValidate?: (input: IFieldState) => Promise; // If specified, overrides the value returned by onValidate. - flagInvalid?: boolean; + forceValidity?: boolean; // If specified, contents will appear as a tooltip on the element and // validation feedback tooltips will be suppressed. tooltipContent?: React.ReactNode; @@ -203,7 +203,7 @@ export default class Field extends React.PureComponent { public render() { const { element, prefixComponent, postfixComponent, className, onValidate, children, - tooltipContent, flagInvalid, tooltipClassName, list, ...inputProps} = this.props; + tooltipContent, forceValidity, tooltipClassName, list, ...inputProps} = this.props; // Set some defaults for the element const ref = input => this.input = input; @@ -228,15 +228,15 @@ export default class Field extends React.PureComponent { postfixContainer = {postfixComponent}; } - const hasValidationFlag = flagInvalid !== null && flagInvalid !== undefined; + const hasValidationFlag = forceValidity !== null && forceValidity !== undefined; const fieldClasses = classNames("mx_Field", `mx_Field_${this.props.element}`, className, { // If we have a prefix element, leave the label always at the top left and // don't animate it, as it looks a bit clunky and would add complexity to do // properly. mx_Field_labelAlwaysTopLeft: prefixComponent, - mx_Field_valid: onValidate && this.state.valid === true, + mx_Field_valid: hasValidationFlag ? forceValidity : onValidate && this.state.valid === true, mx_Field_invalid: hasValidationFlag - ? flagInvalid + ? !forceValidity : onValidate && this.state.valid === false, }); diff --git a/src/components/views/settings/SetIdServer.js b/src/components/views/settings/SetIdServer.js index 23e72e2352..e05fe4f1c3 100644 --- a/src/components/views/settings/SetIdServer.js +++ b/src/components/views/settings/SetIdServer.js @@ -413,7 +413,7 @@ export default class SetIdServer extends React.Component { tooltipContent={this._getTooltip()} tooltipClassName="mx_SetIdServer_tooltip" disabled={this.state.busy} - flagInvalid={!!this.state.error} + forceValidity={this.state.error ? false : null} /> Date: Fri, 26 Jun 2020 18:55:23 +0100 Subject: [PATCH 158/294] Disable spellcheck on the recovery key entry --- .../views/dialogs/secretstorage/AccessSecretStorageDialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js index 5029856f26..8b61af8886 100644 --- a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js +++ b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js @@ -290,7 +290,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent { content =

          {_t("Use your Security Key to continue.")}

          - +
          Date: Fri, 26 Jun 2020 18:58:12 +0100 Subject: [PATCH 159/294] i18n --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9160d48f48..4e5fd63d6c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1796,6 +1796,7 @@ "Enter your Security Phrase or to continue.": "Enter your Security Phrase or to continue.", "Security Key": "Security Key", "Use your Security Key to continue.": "Use your Security Key to continue.", + "Go Back": "Go Back", "Restoring keys from backup": "Restoring keys from backup", "Fetching keys from server...": "Fetching keys from server...", "%(completed)s of %(total)s keys restored": "%(completed)s of %(total)s keys restored", @@ -2138,7 +2139,6 @@ "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.", "Your new session is now verified. Other users will see it as trusted.": "Your new session is now verified. Other users will see it as trusted.", "Without completing security on this session, it won’t have access to encrypted messages.": "Without completing security on this session, it won’t have access to encrypted messages.", - "Go Back": "Go Back", "Failed to re-authenticate due to a homeserver problem": "Failed to re-authenticate due to a homeserver problem", "Incorrect password": "Incorrect password", "Failed to re-authenticate": "Failed to re-authenticate", From 916f60687298e75f2a7e51043b9bead20cc542a7 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 26 Jun 2020 19:07:39 +0100 Subject: [PATCH 160/294] Apparently we need to null check here --- .../views/dialogs/secretstorage/AccessSecretStorageDialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js index 8b61af8886..868eeb2218 100644 --- a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js +++ b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js @@ -110,7 +110,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent { // also clear the file upload control so that the user can upload the same file // the did before (otherwise the onchange wouldn't fire) - this._fileUpload.current.value = null; + if (this._fileUpload.current) this._fileUpload.current.value = null; // We don't use Field's validation here because a) we want it in a separate place rather From 0579c9f748b052c4e7ad547457d8d46946da8c31 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 26 Jun 2020 20:25:38 +0100 Subject: [PATCH 161/294] Fix tests --- .../AccessSecretStorageDialog.js | 6 ++-- .../dialogs/AccessSecretStorageDialog-test.js | 30 +++++++++++-------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js index 868eeb2218..e5b75f4dfc 100644 --- a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js +++ b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js @@ -23,7 +23,6 @@ import * as sdk from '../../../../index'; import {MatrixClientPeg} from '../../../../MatrixClientPeg'; import Field from '../../elements/Field'; import AccessibleButton from '../../elements/AccessibleButton'; -import { decodeRecoveryKey } from 'matrix-js-sdk/src/crypto/recoverykey'; import { _t } from '../../../../languageHandler'; @@ -86,8 +85,9 @@ export default class AccessSecretStorageDialog extends React.PureComponent { } try { - const decodedKey = decodeRecoveryKey(this.state.recoveryKey); - const correct = await MatrixClientPeg.get().checkSecretStorageKey( + const cli = MatrixClientPeg.get(); + const decodedKey = cli.keyBackupKeyFromRecoveryKey(this.state.recoveryKey); + const correct = await cli.checkSecretStorageKey( decodedKey, this.props.keyInfo, ); this.setState({ diff --git a/test/components/views/dialogs/AccessSecretStorageDialog-test.js b/test/components/views/dialogs/AccessSecretStorageDialog-test.js index c754a4b607..71413a2978 100644 --- a/test/components/views/dialogs/AccessSecretStorageDialog-test.js +++ b/test/components/views/dialogs/AccessSecretStorageDialog-test.js @@ -40,19 +40,20 @@ describe("AccessSecretStorageDialog", function() { testInstance.getInstance()._onRecoveryKeyNext(e); }); - it("Considers a valid key to be valid", function() { + it("Considers a valid key to be valid", async function() { const testInstance = TestRenderer.create( true} />, ); - const v = "asfd"; + const v = "asdf"; const e = { target: { value: v } }; stubClient(); - MatrixClientPeg.get().isValidRecoveryKey = function(k) { - return k == v; - }; + MatrixClientPeg.get().keyBackupKeyFromRecoveryKey = () => 'a raw key'; + MatrixClientPeg.get().checkSecretStorageKey = () => true; testInstance.getInstance()._onRecoveryKeyChange(e); + // force a validation now because it debounces + await testInstance.getInstance()._validateRecoveryKey(); const { recoveryKeyValid } = testInstance.getInstance().state; expect(recoveryKeyValid).toBe(true); }); @@ -65,17 +66,20 @@ describe("AccessSecretStorageDialog", function() { ); const e = { target: { value: "a" } }; stubClient(); - MatrixClientPeg.get().isValidRecoveryKey = () => true; + MatrixClientPeg.get().keyBackupKeyFromRecoveryKey = () => { + throw new Error("that's no key"); + }; testInstance.getInstance()._onRecoveryKeyChange(e); - await testInstance.getInstance()._onRecoveryKeyNext({ preventDefault: () => {} }); - const { keyMatches } = testInstance.getInstance().state; - expect(keyMatches).toBe(false); + // force a validation now because it debounces + await testInstance.getInstance()._validateRecoveryKey(); + + const { recoveryKeyValid, recoveryKeyCorrect } = testInstance.getInstance().state; + expect(recoveryKeyValid).toBe(false); + expect(recoveryKeyCorrect).toBe(false); const notification = testInstance.root.findByProps({ - className: "mx_AccessSecretStorageDialog_keyStatus", + className: "mx_AccessSecretStorageDialog_recoveryKeyFeedback mx_AccessSecretStorageDialog_recoveryKeyFeedback_invalid", }); - expect(notification.props.children).toEqual( - ["\uD83D\uDC4E ", "Unable to access secret storage. Please verify that you " + - "entered the correct recovery key."]); + expect(notification.props.children).toEqual("Invalid Recovery Key"); done(); }); From 2969820371b60976a995d3a159f3f740610d1955 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 26 Jun 2020 20:31:22 +0100 Subject: [PATCH 162/294] LINT --- .../components/views/dialogs/AccessSecretStorageDialog-test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/components/views/dialogs/AccessSecretStorageDialog-test.js b/test/components/views/dialogs/AccessSecretStorageDialog-test.js index 71413a2978..5a8dcbf763 100644 --- a/test/components/views/dialogs/AccessSecretStorageDialog-test.js +++ b/test/components/views/dialogs/AccessSecretStorageDialog-test.js @@ -77,7 +77,8 @@ describe("AccessSecretStorageDialog", function() { expect(recoveryKeyValid).toBe(false); expect(recoveryKeyCorrect).toBe(false); const notification = testInstance.root.findByProps({ - className: "mx_AccessSecretStorageDialog_recoveryKeyFeedback mx_AccessSecretStorageDialog_recoveryKeyFeedback_invalid", + className: "mx_AccessSecretStorageDialog_recoveryKeyFeedback " + + "mx_AccessSecretStorageDialog_recoveryKeyFeedback_invalid", }); expect(notification.props.children).toEqual("Invalid Recovery Key"); done(); From 10492fe72f4721b6acac7610b3068bc6c296cf63 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sat, 27 Jun 2020 18:30:15 +0100 Subject: [PATCH 163/294] fix StyledRadioGroup React key warning Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/elements/StyledRadioGroup.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/views/elements/StyledRadioGroup.tsx b/src/components/views/elements/StyledRadioGroup.tsx index 050a8b7adb..ded1342462 100644 --- a/src/components/views/elements/StyledRadioGroup.tsx +++ b/src/components/views/elements/StyledRadioGroup.tsx @@ -41,9 +41,8 @@ function StyledRadioGroup({name, definitions, value, className }; return - {definitions.map(d => + {definitions.map(d => Date: Sun, 28 Jun 2020 04:08:06 +0800 Subject: [PATCH 164/294] Hide room list show less button if it would do nothing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: ☕ Tim On the new rooms list, if the show less button wouldn't result in a smaller list, don't show it. Fixes vector-im/riot-web#14219 --- src/components/views/rooms/RoomSublist2.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index 79efa565b7..d34ddf090d 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -393,7 +393,7 @@ export default class RoomSublist2 extends React.Component { {showMoreText}
          ); - } else if (tiles.length <= nVisible && tiles.length > this.props.layout.minVisibleTiles) { + } else if (tiles.length <= nVisible && tiles.length > this.props.layout.defaultVisibleTiles) { // we have all tiles visible - add a button to show less let showLessText = ( From d53535c0892b4bb55e7a4b27be6855fe5d3bf9c7 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Sun, 28 Jun 2020 03:35:39 +0000 Subject: [PATCH 165/294] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2344 of 2344 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 58 ++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index 325f657af1..9384e8846a 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2536,5 +2536,61 @@ "Modern": "現代", "Use a system font": "使用系統字型", "System font name": "系統字型名稱", - "The authenticity of this encrypted message can't be guaranteed on this device.": "無法在此裝置上保證加密訊息的真實性。" + "The authenticity of this encrypted message can't be guaranteed on this device.": "無法在此裝置上保證加密訊息的真實性。", + "You joined the call": "您加入了通話", + "%(senderName)s joined the call": "%(senderName)s 加入了通話", + "Call in progress": "通話進行中", + "You left the call": "您離開了通話", + "%(senderName)s left the call": "%(senderName)s 離開了通話", + "Call ended": "通話結束", + "You started a call": "您開始了通話", + "%(senderName)s started a call": "%(senderName)s 開始了通話", + "Waiting for answer": "正在等待回應", + "%(senderName)s is calling": "%(senderName)s 正在通話", + "You created the room": "您建立了聊天室", + "%(senderName)s created the room": "%(senderName)s 建立了聊天室", + "You made the chat encrypted": "您讓聊天加密", + "%(senderName)s made the chat encrypted": "%(senderName)s 讓聊天加密", + "You made history visible to new members": "您讓歷史紀錄對新成員可見", + "%(senderName)s made history visible to new members": "%(senderName)s 讓歷史紀錄對新成員可見", + "You made history visible to anyone": "您讓歷史紀錄對所有人可見", + "%(senderName)s made history visible to anyone": "%(senderName)s 讓歷史紀錄對所有人可見", + "You made history visible to future members": "您讓歷史紀錄對未來成員可見", + "%(senderName)s made history visible to future members": "%(senderName)s 讓歷史紀錄對未來成員可見", + "You were invited": "您被邀請", + "%(targetName)s was invited": "%(targetName)s 被邀請", + "You left": "您離開", + "%(targetName)s left": "%(targetName)s 離開", + "You were kicked (%(reason)s)": "您被踢除(%(reason)s)", + "%(targetName)s was kicked (%(reason)s)": "%(targetName)s 被踢除(%(reason)s)", + "You were kicked": "您被踢除", + "%(targetName)s was kicked": "%(targetName)s 被踢除", + "You rejected the invite": "您回絕了邀請", + "%(targetName)s rejected the invite": "%(targetName)s 回絕了邀請", + "You were uninvited": "您被取消邀請", + "%(targetName)s was uninvited": "%(targetName)s 被取消邀請", + "You were banned (%(reason)s)": "您被封鎖(%(reason)s)", + "%(targetName)s was banned (%(reason)s)": "%(targetName)s 被封鎖(%(reason)s)", + "You were banned": "您被封鎖", + "%(targetName)s was banned": "%(targetName)s 被封鎖", + "You joined": "您加入", + "%(targetName)s joined": "%(targetName)s 加入", + "You changed your name": "您變更了您的名稱", + "%(targetName)s changed their name": "%(targetName)s 變更了他們的名稱", + "You changed your avatar": "您變更了您的大頭貼", + "%(targetName)s changed their avatar": "%(targetName)s 變更了他們的大頭貼", + "%(senderName)s %(emote)s": "%(senderName)s %(emote)s", + "%(senderName)s: %(message)s": "%(senderName)s: %(message)s", + "You changed the room name": "您變更了聊天室名稱", + "%(senderName)s changed the room name": "%(senderName)s 變更了聊天室名稱", + "%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s", + "%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s", + "You uninvited %(targetName)s": "您取消邀請了 %(targetName)s", + "%(senderName)s uninvited %(targetName)s": "%(senderName)s 取消邀請了 %(targetName)s", + "You invited %(targetName)s": "您邀請了 %(targetName)s", + "%(senderName)s invited %(targetName)s": "%(senderName)s 邀請了 %(targetName)s", + "You changed the room topic": "您變更了聊天室主題", + "%(senderName)s changed the room topic": "%(senderName)s 變更了聊天室主題", + "New spinner design": "新的微調器設計", + "Use a more compact ‘Modern’ layout": "使用更簡潔的「現代」佈局" } From 2f5fdd045bdd03fa340849d74b644b52082066fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Fri, 26 Jun 2020 16:26:31 +0000 Subject: [PATCH 166/294] Translated using Weblate (Estonian) Currently translated at 73.8% (1731 of 2344 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/et/ --- src/i18n/strings/et.json | 130 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index 32c069f6c1..7f029f9328 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -1685,5 +1685,133 @@ "All settings": "Kõik seadistused", "Archived rooms": "Arhiveeritud jututoad", "Feedback": "Tagasiside", - "Account settings": "Kasutajakonto seadistused" + "Account settings": "Kasutajakonto seadistused", + "Use Single Sign On to continue": "Jätkamiseks kasuta ühekordset sisselogimist", + "Confirm adding this email address by using Single Sign On to prove your identity.": "Kinnita selle e-posti aadressi lisamine kasutades ühekordset sisselogimist oma isiku tuvastamiseks.", + "Single Sign On": "SSO Ühekordne sisselogimine", + "Confirm adding email": "Kinnita e-posti aadressi lisamine", + "Click the button below to confirm adding this email address.": "Klõpsi järgnevat nuppu e-posti aadressi lisamise kinnitamiseks.", + "Confirm adding this phone number by using Single Sign On to prove your identity.": "Kinnita selle telefoninumbri lisamine kasutades ühekordset sisselogimist oma isiku tuvastamiseks.", + "Confirm adding phone number": "Kinnita telefoninumbri lisamine", + "Click the button below to confirm adding this phone number.": "Klõpsi järgnevat nuppu telefoninumbri lisamise kinnitamiseks.", + "Add Phone Number": "Lisa telefoninumber", + "Default": "Tavakasutaja", + "Restricted": "Piiratud õigustega kasutaja", + "Moderator": "Moderaator", + "Admin": "Peakasutaja", + "Custom (%(level)s)": "Kohandatud õigused (%(level)s)", + "Failed to invite": "Kutse saatmine ei õnnestunud", + "Operation failed": "Toiming ei õnnestunud", + "Failed to invite users to the room:": "Kasutajate kutsumine jututuppa ei õnnestunud:", + "Failed to invite the following users to the %(roomName)s room:": "Järgnevate kasutajate kutsumine %(roomName)s jututuppa ei õnnestunud:", + "You need to be logged in.": "Sa peaksid olema sisse loginud.", + "You need to be able to invite users to do that.": "Selle tegevuse jaoks peaks sul olema õigus teistele kasutajatele kutse saatmiseks.", + "Unable to create widget.": "Vidina loomine ei õnnestunud.", + "Missing roomId.": "Jututoa tunnus ehk roomId on puudu.", + "Failed to send request.": "Päringu saatmine ei õnnestunud.", + "This room is not recognised.": "Seda jututuba ei õnnestu ära tunda.", + "Power level must be positive integer.": "Õiguste tase peab olema positiivne täisarv.", + "You are not in this room.": "Sa ei asu selles jututoas.", + "You do not have permission to do that in this room.": "Sinul pole selle toimingu jaoks selles jututoas õigusi.", + "Missing room_id in request": "Päringus puudub jututoa tunnus ehk room_id", + "Room %(roomId)s not visible": "Jututuba %(roomId)s ei ole nähtav", + "Missing user_id in request": "Päringus puudub kasutaja tunnus ehk user_id", + "Messages": "Sõnumid", + "Actions": "Tegevused", + "Other": "Muud", + "Usage": "Kasutus", + "Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Lisa ¯\\_(ツ)_/¯ smaili vormindamata teksti algusesse", + "Sends a message as plain text, without interpreting it as markdown": "Saadab sõnumi vormindamata tekstina ega tõlgenda seda markdown-vormindusena", + "Sends a message as html, without interpreting it as markdown": "Saadab sõnumi html'ina ega tõlgenda seda markdown-vormindusena", + "/ddg is not a command": "/ddg ei ole käsk", + "You joined the call": "Sina liitusid kõnega", + "%(senderName)s joined the call": "%(senderName)s liitus kõnega", + "Call in progress": "Kõne on pooleli", + "You left the call": "Sa lahkusid kõnest", + "%(senderName)s left the call": "%(senderName)s lahkus kõnest", + "Call ended": "Kõne lõppes", + "You started a call": "Sa alustasid kõnet", + "%(senderName)s started a call": "%(senderName)s alustas kõnet", + "Waiting for answer": "Ootan kõnele vastamist", + "%(senderName)s is calling": "%(senderName)s helistab", + "You created the room": "Sa lõid jututoa", + "%(senderName)s created the room": "%(senderName)s lõi jututoa", + "You made the chat encrypted": "Sina võtsid vestlusel kasutuse krüptimise", + "%(senderName)s made the chat encrypted": "%(senderName)s võttis vestlusel kasutuse krüptimise", + "You made history visible to new members": "Sina tegid jututoa ajaloo loetavaks uuetele liikmetele", + "%(senderName)s made history visible to new members": "%(senderName)s tegi jututoa ajaloo loetavaks uuetele liikmetele", + "You made history visible to anyone": "Sina tegi jututoa ajaloo loetavaks kõikidele", + "%(senderName)s made history visible to anyone": "%(senderName)s tegi jututoa ajaloo loetavaks kõikidele", + "You made history visible to future members": "Sina tegid jututoa ajaloo loetavaks tulevastele liikmetele", + "%(senderName)s made history visible to future members": "%(senderName)s tegi jututoa ajaloo loetavaks tulevastele liikmetele", + "You were invited": "Sina said kutse", + "%(targetName)s was invited": "%(targetName)s sai kutse", + "You left": "Sina lahkusid", + "%(targetName)s left": "%(targetName)s lahkus", + "You were kicked (%(reason)s)": "Sind müksati jututoast välja (%(reason)s)", + "%(targetName)s was kicked (%(reason)s)": "%(targetName)s müksati jututoast välja (%(reason)s)", + "You were kicked": "Sind müksati jututoast välja", + "%(targetName)s was kicked": "%(targetName)s müksati jututoast välja", + "You rejected the invite": "Sa lükkasid kutse tagasi", + "%(targetName)s rejected the invite": "%(targetName)s lükkas kutse tagasi", + "You were uninvited": "Sinult võeti kutse tagasi", + "%(targetName)s was uninvited": "%(targetName)s'lt võeti kutse tagasi", + "You joined": "Sina liitusid", + "%(targetName)s joined": "%(targetName)s liitus", + "You changed your name": "Sa muutsid oma nime", + "%(targetName)s changed their name": "%(targetName)s muutis oma nime", + "You changed your avatar": "Sa muutsid oma tunnuspilti", + "%(targetName)s changed their avatar": "%(targetName)s muutis oma tunnuspilti", + "%(senderName)s %(emote)s": "%(senderName)s %(emote)s", + "%(senderName)s: %(message)s": "%(senderName)s: %(message)s", + "You changed the room name": "Sina muutsid jututoa nime", + "%(senderName)s changed the room name": "%(senderName)s muutis jututoa nime", + "%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s", + "%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s", + "You uninvited %(targetName)s": "Sina võtsid tagasi kutse kasutajalt %(targetName)s", + "%(senderName)s uninvited %(targetName)s": "%(senderName)s võttis kutse tagasi kasutajalt %(targetName)s", + "You invited %(targetName)s": "Sina kutsusid kasutajat %(targetName)s", + "%(senderName)s invited %(targetName)s": "%(senderName)s kutsus kasutajat %(targetName)s", + "You changed the room topic": "Sina muutsid jututoa teemat", + "%(senderName)s changed the room topic": "%(senderName)s muutis jututoa teemat", + "Use the improved room list (will refresh to apply changes)": "Kasuta parandaatud jututubade loendit (muudatuse jõustamine eeldab andmete uuesti laadimist)", + "Enable IRC layout option in the appearance tab": "Näita välimuse seadistustes IRC-tüüpi paigutuse valikut", + "Use custom size": "Kasuta kohandatud suurust", + "Use a more compact ‘Modern’ layout": "Kasuta veel kompaktsemat \"moodsat\" paigutust", + "Cross-signing public keys:": "Avalikud võtmed risttunnustamise jaoks:", + "in memory": "on mälus", + "not found": "pole leitavad", + "Delete %(count)s sessions|other": "Kustuta %(count)s sessiooni", + "Delete %(count)s sessions|one": "Kustuta %(count)s sessioon", + "ID": "Kasutaja ID", + "Public Name": "Avalik nimi", + "Last seen": "Viimati nähtud", + "Manage": "Halda", + "Enable": "Võta kasutusele", + "Error saving email notification preferences": "E-posti teel saadetavate teavituste eelistuste salvestamisel tekkis viga", + "An error occurred whilst saving your email notification preferences.": "E-posti teel saadetavate teavituste eelistuste salvestamisel tekkis viga.", + "Keywords": "Märksõnad", + "Enter keywords separated by a comma:": "Sisesta märksõnad ja kasuta eraldajaks koma:", + "Failed to change settings": "Seadistuste muutmine ei õnnestunud", + "Can't update user notification settings": "Kasutaja teavistuste eelistusi ei õnnestunud uuendada", + "Failed to update keywords": "Märksõnade uuendamine ei õnnestunud", + "Messages containing keywords": "Sõnumid, mis sisaldavad märksõnu", + "Notify me for anything else": "Teavita mind kõigest muust", + "Enable notifications for this account": "Võta sellel kasutajakontol kasutusele teavitused", + "Clear notifications": "Eemalda kõik teavitused", + "All notifications are currently disabled for all targets.": "Kõik teavituste liigid on välja lülitatud.", + "Add an email address to configure email notifications": "E-posti teel saadetavate teavituste seadistamiseks lisa e-posti aadress", + "Enable email notifications": "Võta kasutusele e-posti teel saadetavad teavitused", + "Notifications on the following keywords follow rules which can’t be displayed here:": "Alljärgnevate märksõnadega seotud teavitused järgivad reegleid, misa siin ei saa kuvada:", + "Disinvite this user from community?": "Kas võtame sellelt kasutajalt tagasi kutse kogukonnaga liitumiseks?", + "Failed to withdraw invitation": "Kutse tühistamine ei õnnestunud", + "%(role)s in %(roomName)s": "%(role)s jututoas %(roomName)s", + "Failed to change power level": "Õiguste muutmine ei õnnestunud", + "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "Sa ei saa seda muudatust hiljem tagasi pöörata, sest annad teisele kasutajale samad õigused, mis sinul on.", + "Deactivate user?": "Kas blokeerime kasutaja?", + "Deactivating this user will log them out and prevent them from logging back in. Additionally, they will leave all the rooms they are in. This action cannot be reversed. Are you sure you want to deactivate this user?": "Kasutaja blokeerimisel logitakse ta automaatselt välja ning ei lubata enam sisse logida. Lisaks lahkub ta kõikidest jututubadest, mille liige ta parasjagu on. Seda tegevust ei saa tagasi pöörata. Kas sa oled ikka kindel, et soovid selle kasutaja blokeerida?", + "Deactivate user": "Blokeeri kasutaja", + "Failed to deactivate user": "Kasutaja blokeerimine ei õnnestunud", + "This client does not support end-to-end encryption.": "See klient ei toeta läbivat krüptimist.", + "Security": "Turvalisus" } From 4acf87af0b3d5f32f514052e6e0499c27e46035c Mon Sep 17 00:00:00 2001 From: Tuomas Hietala Date: Fri, 26 Jun 2020 16:35:50 +0000 Subject: [PATCH 167/294] Translated using Weblate (Finnish) Currently translated at 94.8% (2222 of 2344 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/fi/ --- src/i18n/strings/fi.json | 52 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/fi.json b/src/i18n/strings/fi.json index 1157daa260..b10366bbc9 100644 --- a/src/i18n/strings/fi.json +++ b/src/i18n/strings/fi.json @@ -2367,5 +2367,55 @@ "Use Recovery Key or Passphrase": "Käytä palautusavainta tai salalausetta", "Use Recovery Key": "Käytä palautusavainta", "Create a Recovery Key": "Luo palautusavain", - "Upgrade your Recovery Key": "Päivitä palautusavaimesi" + "Upgrade your Recovery Key": "Päivitä palautusavaimesi", + "You joined the call": "Liityit puheluun", + "%(senderName)s joined the call": "%(senderName)s liittyi puheluun", + "Call in progress": "Puhelu käynnissä", + "You left the call": "Poistuit puhelusta", + "%(senderName)s left the call": "%(senderName)s poistui puhelusta", + "Call ended": "Puhelu päättyi", + "You started a call": "Aloitit puhelun", + "%(senderName)s started a call": "%(senderName)s aloitti puhelun", + "Waiting for answer": "Odotetaan vastausta", + "%(senderName)s is calling": "%(senderName)s soittaa", + "You created the room": "Loit huoneen", + "%(senderName)s created the room": "%(senderName)s loi huoneen", + "You made the chat encrypted": "Otit salauksen käyttöön keskustelussa", + "%(senderName)s made the chat encrypted": "%(senderName)s otti salauksen käyttöön keskustelussa", + "You made history visible to new members": "Teit historiasta näkyvän uusille jäsenille", + "%(senderName)s made history visible to new members": "%(senderName)s teki historiasta näkyvän uusille jäsenille", + "You made history visible to anyone": "Teit historiasta näkyvän kaikille", + "%(senderName)s made history visible to anyone": "%(senderName)s teki historiasta näkyvän kaikille", + "You made history visible to future members": "Teit historiasta näkyvän tuleville jäsenille", + "%(senderName)s made history visible to future members": "%(senderName)s teki historiasta näkyvän tuleville jäsenille", + "You were invited": "Sinut kutsuttiin", + "%(targetName)s was invited": "%(targetName)s kutsuttiin", + "You left": "Poistuit", + "%(targetName)s left": "%(targetName)s poistui", + "You rejected the invite": "Hylkäsit kutsun", + "%(targetName)s rejected the invite": "%(targetName)s hylkäsi kutsun", + "You were banned (%(reason)s)": "Sait porttikiellon (%(reason)s)", + "%(targetName)s was banned (%(reason)s)": "%(targetName)s sai porttikiellon (%(reason)s)", + "You were banned": "Sait porttikiellon", + "%(targetName)s was banned": "%(targetName)s sai porttikiellon", + "You joined": "Liityit", + "%(targetName)s joined": "%(targetName)s liittyi", + "You changed your name": "Vaihdoit nimeäsi", + "%(targetName)s changed their name": "%(targetName)s vaihtoi nimeään", + "%(senderName)s: %(message)s": "%(senderName)s: %(message)s", + "You changed the room name": "Vaihdoit huoneen nimeä", + "%(senderName)s changed the room name": "%(senderName)s vaihtoi huoneen nimeä", + "%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s", + "%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s", + "You invited %(targetName)s": "Kutsuit käyttäjän %(targetName)s", + "%(senderName)s invited %(targetName)s": "%(senderName)s kutsui käyttäjän %(targetName)s", + "You changed the room topic": "Vaihdoit huoneen aiheen", + "%(senderName)s changed the room topic": "%(senderName)s vaihtoi huoneen aiheen", + "Use custom size": "Käytä mukautettua kokoa", + "Use a more compact ‘Modern’ layout": "Käytä tiiviimpää 'modernia' asettelua", + "Use a system font": "Käytä järjestelmän fonttia", + "System font name": "Järjestelmän fontin nimi", + "Message layout": "Viestiasettelu", + "Compact": "Tiivis", + "Modern": "Moderni" } From c16f8aaada2d7a83e0649af1a0efd3ce4db54bd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Sat, 27 Jun 2020 10:42:51 +0000 Subject: [PATCH 168/294] Translated using Weblate (French) Currently translated at 100.0% (2344 of 2344 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/fr/ --- src/i18n/strings/fr.json | 57 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index fa66048da0..f781df55e2 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -2538,5 +2538,60 @@ "Use a system font": "Utiliser une police du système", "System font name": "Nom de la police du système", "The authenticity of this encrypted message can't be guaranteed on this device.": "L’authenticité de ce message chiffré ne peut pas être garantie sur cet appareil.", - "Use a more compact ‘Modern’ layout": "Utiliser une mise en page « moderne » plus compacte" + "Use a more compact ‘Modern’ layout": "Utiliser une mise en page « moderne » plus compacte", + "You joined the call": "Vous avez rejoint l’appel", + "%(senderName)s joined the call": "%(senderName)s a rejoint l’appel", + "Call in progress": "Appel en cours", + "You left the call": "Vous avez quitté l’appel", + "%(senderName)s left the call": "%(senderName)s a quitté l’appel", + "Call ended": "Appel terminé", + "You started a call": "Vous avez démarré un appel", + "%(senderName)s started a call": "%(senderName)s a démarré un appel", + "Waiting for answer": "En attente d’une réponse", + "%(senderName)s is calling": "%(senderName)s appelle", + "You created the room": "Vous avez créé le salon", + "%(senderName)s created the room": "%(senderName)s a créé le salon", + "You made the chat encrypted": "Vous avez activé le chiffrement de la discussion", + "%(senderName)s made the chat encrypted": "%(senderName)s a activé le chiffrement de la discussion", + "You made history visible to new members": "Vous avez rendu l’historique visible aux nouveaux membres", + "%(senderName)s made history visible to new members": "%(senderName)s a rendu l’historique visible aux nouveaux membres", + "You made history visible to anyone": "Vous avez rendu l’historique visible à tout le monde", + "%(senderName)s made history visible to anyone": "%(senderName)s a rendu l’historique visible à tout le monde", + "You made history visible to future members": "Vous avez rendu l’historique visible aux futurs membres", + "%(senderName)s made history visible to future members": "%(senderName)s a rendu l’historique visible aux futurs membres", + "You were invited": "Vous avez été invité", + "%(targetName)s was invited": "%(targetName)s a été invité·e", + "You left": "Vous êtes parti·e", + "%(targetName)s left": "%(targetName)s est parti·e", + "You were kicked (%(reason)s)": "Vous avez été expulsé·e (%(reason)s)", + "%(targetName)s was kicked (%(reason)s)": "%(targetName)s a été expulsé·e (%(reason)s)", + "You were kicked": "Vous avez été expulsé·e", + "%(targetName)s was kicked": "%(targetName)s a été expulsé·e", + "You rejected the invite": "Vous avez rejeté l’invitation", + "%(targetName)s rejected the invite": "%(targetName)s a rejeté l’invitation", + "You were uninvited": "Votre invitation a été révoquée", + "%(targetName)s was uninvited": "L’invitation de %(targetName)s a été révoquée", + "You were banned (%(reason)s)": "Vous avez été banni·e (%(reason)s)", + "%(targetName)s was banned (%(reason)s)": "%(targetName)s a été banni·e (%(reason)s)", + "You were banned": "Vous avez été banni·e", + "%(targetName)s was banned": "%(targetName)s a été banni·e", + "You joined": "Vous avez rejoint le salon", + "%(targetName)s joined": "%(targetName)s a rejoint le salon", + "You changed your name": "Vous avez changé votre nom", + "%(targetName)s changed their name": "%(targetName)s a changé son nom", + "You changed your avatar": "Vous avez changé votre avatar", + "%(targetName)s changed their avatar": "%(targetName)s a changé son avatar", + "%(senderName)s %(emote)s": "%(senderName)s %(emote)s", + "%(senderName)s: %(message)s": "%(senderName)s : %(message)s", + "You changed the room name": "Vous avez changé le nom du salon", + "%(senderName)s changed the room name": "%(senderName)s a changé le nom du salon", + "%(senderName)s: %(reaction)s": "%(senderName)s : %(reaction)s", + "%(senderName)s: %(stickerName)s": "%(senderName)s : %(stickerName)s", + "You uninvited %(targetName)s": "Vous avez révoqué l’invitation de %(targetName)s", + "%(senderName)s uninvited %(targetName)s": "%(senderName)s a révoqué l’invitation de %(targetName)s", + "You invited %(targetName)s": "Vous avez invité %(targetName)s", + "%(senderName)s invited %(targetName)s": "%(senderName)s a invité %(targetName)s", + "You changed the room topic": "Vous avez changé le sujet du salon", + "%(senderName)s changed the room topic": "%(senderName)s a changé le sujet du salon", + "New spinner design": "Nouveau design du spinner" } From 11bc33853cef51dbd186b9e58cefdaf544e9d8a2 Mon Sep 17 00:00:00 2001 From: XoseM Date: Fri, 26 Jun 2020 13:50:42 +0000 Subject: [PATCH 169/294] Translated using Weblate (Galician) Currently translated at 100.0% (2344 of 2344 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/gl/ --- src/i18n/strings/gl.json | 148 ++++++++++++++++++++++++++------------- 1 file changed, 101 insertions(+), 47 deletions(-) diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index d88c3e95c6..b0b61e3d40 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -1,14 +1,14 @@ { - "This email address is already in use": "Xa se está a usar este correo", + "This email address is already in use": "Xa se está a usar este email", "This phone number is already in use": "Xa se está a usar este teléfono", "Failed to verify email address: make sure you clicked the link in the email": "Fallo na verificación do enderezo de correo: asegúrese de ter picado na ligazón do correo", - "The remote side failed to pick up": "O interlocutor non respondeu", + "The remote side failed to pick up": "O correspondente non respondeu", "Unable to capture screen": "Non se puido capturar a pantalla", "Existing Call": "Rexistro de chamadas", - "You are already in a call.": "Xa está nunha chamada.", + "You are already in a call.": "Xa estás nunha chamada.", "VoIP is unsupported": "Sen soporte para VoIP", - "You cannot place VoIP calls in this browser.": "Non pode establecer chamadas VoIP neste navegador.", - "You cannot place a call with yourself.": "Non pode facer unha chamada a si mesmo.", + "You cannot place VoIP calls in this browser.": "Non poden establecer chamadas VoIP neste navegador.", + "You cannot place a call with yourself.": "Non podes facer unha chamada a ti mesma.", "Warning!": "Aviso!", "Call Failed": "Fallou a chamada", "Review Devices": "Revisar dispositivos", @@ -51,9 +51,9 @@ "Add rooms to the community": "Engadir salas á comunidade", "Room name or alias": "Nome da sala ou alcume", "Add to community": "Engadir á comunidade", - "Failed to invite the following users to %(groupId)s:": "Fallo ao convidar os seguintes usuarios a %(groupId)s:", - "Failed to invite users to community": "Houbo un fallo convidando usuarios á comunidade", - "Failed to invite users to %(groupId)s": "Houbo un fallo convidando usuarios a %(groupId)s", + "Failed to invite the following users to %(groupId)s:": "Fallo ao convidar ás seguintes usuarias a %(groupId)s:", + "Failed to invite users to community": "Houbo un fallo convidando usuarias á comunidade", + "Failed to invite users to %(groupId)s": "Houbo un fallo convidando usuarias a %(groupId)s", "Failed to add the following rooms to %(groupId)s:": "Fallo ao engadir as seguintes salas a %(groupId)s:", "Riot does not have permission to send you notifications - please check your browser settings": "Riot non ten permiso para enviarlle notificacións: comprobe os axustes do navegador", "Riot was not given permission to send notifications - please try again": "Riot non ten permiso para enviar notificacións: inténteo de novo", @@ -67,7 +67,7 @@ "Start a chat": "Iniciar unha conversa", "Operation failed": "Fallou a operación", "Failed to invite": "Fallou o convite", - "Failed to invite the following users to the %(roomName)s room:": "Houbo un fallo convidando os seguintes usuarios á sala %(roomName)s:", + "Failed to invite the following users to the %(roomName)s room:": "Houbo un fallo convidando as seguintes usuarias á sala %(roomName)s:", "You need to be logged in.": "Precisa estar conectada.", "You need to be able to invite users to do that.": "Precisa autorización para convidar a outros usuarias para poder facer iso.", "Unable to create widget.": "Non se puido crear o trebello.", @@ -85,7 +85,7 @@ "Unrecognised room alias:": "Alcumes de sala non recoñecidos:", "Ignored user": "Usuaria ignorada", "You are now ignoring %(userId)s": "Agora está a ignorar %(userId)s", - "Unignored user": "Usuarios non ignorados", + "Unignored user": "Usuarias non ignoradas", "You are no longer ignoring %(userId)s": "Xa non está a ignorar a %(userId)s", "Verified key": "Chave verificada", "Reason": "Razón", @@ -147,7 +147,7 @@ "Enable automatic language detection for syntax highlighting": "Activar a detección automática de idioma para o resalte da sintaxe", "Automatically replace plain text Emoji": "Substituír automaticamente Emoji en texto plano", "Enable inline URL previews by default": "Activar por defecto as vistas previas en liña de URL", - "Enable URL previews for this room (only affects you)": "Activar avista previa de URL nesta sala (só lle afecta a vostede)", + "Enable URL previews for this room (only affects you)": "Activar avista previa de URL nesta sala (só che afesta a ti)", "Enable URL previews by default for participants in this room": "Activar a vista previa de URL por defecto para as participantes nesta sala", "Room Colour": "Cor da sala", "Active call (%(roomName)s)": "Chamada activa (%(roomName)s)", @@ -200,15 +200,15 @@ "device id: ": "id dispositivo: ", "Disinvite": "Retirar convite", "Kick": "Expulsar", - "Disinvite this user?": "Retirar convite a este usuario?", - "Kick this user?": "Expulsar este usuario?", + "Disinvite this user?": "Retirar convite a esta usuaria?", + "Kick this user?": "Expulsar esta usuaria?", "Failed to kick": "Fallo ao expulsar", "Unban": "Non bloquear", "Ban": "Bloquear", - "Unban this user?": "Non bloquear este usuario?", - "Ban this user?": "Bloquear a este usuario?", - "Failed to ban user": "Fallo ao bloquear usuario", - "Failed to mute user": "Fallo ao acalar usuario", + "Unban this user?": "¿Non bloquear esta usuaria?", + "Ban this user?": "¿Bloquear a esta usuaria?", + "Failed to ban user": "Fallo ao bloquear usuaria", + "Failed to mute user": "Fallo ó silenciar usuaria", "Failed to toggle moderator status": "Fallo ao mudar a estado de moderador", "Failed to change power level": "Fallo ao cambiar o nivel de permisos", "Are you sure?": "Está segura?", @@ -288,9 +288,9 @@ "Banned by %(displayName)s": "Non aceptado por %(displayName)s", "unknown error code": "código de fallo descoñecido", "Failed to forget room %(errCode)s": "Fallo ao esquecer sala %(errCode)s", - "Privileged Users": "Usuarios con privilexios", - "No users have specific privileges in this room": "Non hai usuarios con privilexios específicos nesta sala", - "Banned users": "Usuarios excluídos", + "Privileged Users": "Usuarias con privilexios", + "No users have specific privileges in this room": "Non hai usuarias con privilexios específicos nesta sala", + "Banned users": "Usuarias excluídas", "This room is not accessible by remote Matrix servers": "Esta sala non é accesible por servidores Matrix remotos", "Leave room": "Deixar a sala", "Favourite": "Favorita", @@ -378,9 +378,9 @@ "You're not currently a member of any communities.": "Ate o momento non é membro de ningunha comunidade.", "Unknown Address": "Enderezo descoñecido", "Allow": "Permitir", - "Delete Widget": "Eliminar trebello", - "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Quitando un trebello elimínao para todas os usuarios desta sala. Está seguro de querer eliminar este trebello?", - "Delete widget": "Eliminar trebello", + "Delete Widget": "Eliminar widget", + "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Quitando un trebello elimínalo para todas as usuarias desta sala. ¿tes certeza de querer eliminar este widget?", + "Delete widget": "Eliminar widget", "Minimize apps": "Minimizar apps", "Edit": "Editar", "Create new room": "Crear unha nova sala", @@ -459,7 +459,7 @@ "Try using one of the following valid address types: %(validTypesList)s.": "Intentar utilizar algún dos seguintes tipos de enderezo válidos: %(validTypesList)s.", "You have entered an invalid address.": "Introduciu un enderezo non válido.", "Confirm Removal": "Confirme a retirada", - "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Está certa de que quere quitar (eliminar) este evento? Saiba que si elimina un nome de sala ou cambia o asunto, podería desfacer o cambio.", + "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Estás certa de que queres quitar (eliminar) este evento? Debes saber que se eliminas un nome de sala ou cambias o asunto, poderías desfacer o cambio.", "Community IDs may only contain characters a-z, 0-9, or '=_-./'": "Os ID de comunidade só poden conter caracteres a-z, 0-9, or '=_-./'", "Community IDs cannot be empty.": "O ID de comunidade non pode quedar baldeiro.", "Something went wrong whilst creating your community": "Algo fallou mentres se creaba a súa comunidade", @@ -481,7 +481,7 @@ "Ignore request": "Ignorar petición", "Encryption key request": "Petición de chave de cifrado", "Unable to restore session": "Non se puido restaurar a sesión", - "If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "Si anteriormente utilizou unha versión máis recente de Riot, a súa sesión podería non ser compatible con esta versión. Peche esta ventá e volva a versión máis recente.", + "If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "Se anteriormente utilizaches unha versión máis recente de Riot, a túa sesión podería non ser compatible con esta versión. Pecha esta ventá e volve á versión máis recente.", "Invalid Email Address": "Enderezo de correo non válido", "This doesn't appear to be a valid email address": "Este non semella ser un enderezo de correo válido", "Verification Pending": "Verificación pendente", @@ -513,9 +513,9 @@ "Add a Room": "Engadir unha sala", "Failed to remove the room from the summary of %(groupId)s": "Algo fallou ao quitar a sala do resumo de %(groupId)s", "The room '%(roomName)s' could not be removed from the summary.": "A sala '%(roomName)s' non se puido eliminar do resumo.", - "Add users to the community summary": "Engadir usuarios ao resumo da comunidade", + "Add users to the community summary": "Engadir usuarias ó resumo da comunidade", "Who would you like to add to this summary?": "A quen desexa engadir a este resumo?", - "Failed to add the following users to the summary of %(groupId)s:": "Algo fallou ao engadir aos seguintes usuarios ao resumo de %(groupId)s:", + "Failed to add the following users to the summary of %(groupId)s:": "Algo fallou ó engadir ás seguintes usuarias ó resumo de %(groupId)s:", "Add a User": "Engadir unha usuaria", "Failed to remove a user from the summary of %(groupId)s": "Algo fallou ao eliminar a usuaria do resumo de %(groupId)s", "The user '%(displayName)s' could not be removed from the summary.": "A usuaria '%(displayName)s' non se puido eliminar do resumo.", @@ -530,9 +530,9 @@ "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.": "Estas salas móstranse aos membros da comunidade na páxina da comunidade. Os participantes da comunidade poden unirse ás salas premendo nelas.", "Add rooms to this community": "Engadir salas a esta comunidade", "Featured Rooms:": "Salas destacadas:", - "Featured Users:": "Usuarios destacados:", + "Featured Users:": "Usuarias destacadas:", "%(inviter)s has invited you to join this community": "%(inviter)s convidoute a entrar nesta comunidade", - "You are an administrator of this community": "Vostede administra esta comunidade", + "You are an administrator of this community": "Administras esta comunidade", "You are a member of this community": "É membro desta comunidade", "Your community hasn't got a Long Description, a HTML page to show to community members.
          Click here to open settings and give it one!": "A súa comunidade non ten unha descrición longa, ou unha páxina HTML que lle mostrar aos seus participantes.
          Pulse aquí para abrir os axustes e publicar unha!", "Long Description (HTML)": "Descrición longa (HTML)", @@ -592,7 +592,7 @@ "Import E2E room keys": "Importar chaves E2E da sala", "Cryptography": "Criptografía", "Analytics": "Analytics", - "Riot collects anonymous analytics to allow us to improve the application.": "Riot recolle información analítica anónima para permitirnos mellorar o aplicativo.", + "Riot collects anonymous analytics to allow us to improve the application.": "Riot recolle información analítica anónima para permitirnos mellorar a aplicación.", "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.": "A intimidade impórtanos, así que non recollemos información personal ou identificable nos datos dos nosos análises.", "Learn more about how we use analytics.": "Saber máis sobre como utilizamos analytics.", "Labs": "Labs", @@ -651,7 +651,7 @@ "Emoji": "Emoji", "Notify the whole room": "Notificar a toda a sala", "Room Notification": "Notificación da sala", - "Users": "Usuarios", + "Users": "Usuarias", "unknown device": "dispositivo descoñecido", "NOT verified": "Non validado", "verified": "validado", @@ -676,12 +676,12 @@ "Confirm passphrase": "Confirme a frase de paso", "Export": "Exportar", "Import room keys": "Importar chaves de sala", - "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.": "Este proceso permítelle importar chaves de cifrado que vostede exportou de outro cliente Matrix. Así poderá descifrar calquera mensaxe que o outro cliente puidese cifrar.", + "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.": "Este proceso permíteche importar chaves de cifrado que exportaches doutro cliente Matrix. Así poderás descifrar calquera mensaxe que o outro cliente puidese cifrar.", "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "O ficheiro exportado estará protexido con unha frase de paso. Debe introducir aquí esa frase de paso para descifrar o ficheiro.", "File to import": "Ficheiro a importar", "Import": "Importar", "The information being sent to us to help make Riot.im better includes:": "A información enviada a Riot.im para axudarnos a mellorar inclúe:", - "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Se esta páxina inclúe información identificable como ID de grupo, usuario ou sala, estes datos son eliminados antes de ser enviados ao servidor.", + "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Se esta páxina inclúe información identificable como ID de grupo, usuaria ou sala, estes datos son eliminados antes de ser enviados ó servidor.", "The platform you're on": "A plataforma na que está", "The version of Riot.im": "A versión de Riot.im", "Your language of choice": "A súa preferencia de idioma", @@ -703,12 +703,12 @@ "Display your community flair in rooms configured to show it.": "Mostrar a popularidade da túa comunidade nas salas configuradas para que a mostren.", "Did you know: you can use communities to filter your Riot.im experience!": "Sabías que podes usar as comunidades para filtrar a túa experiencia en Riot.im!", "To set up a filter, drag a community avatar over to the filter panel on the far left hand side of the screen. You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "Para establecer un filtro, arrastra un avatar da comunidade sobre o panel de filtros na parte esquerda da pantalla. Podes premer nun avatar no panel de filtrado en calquera momento para ver só salas e xente asociada a esa comunidade.", - "Deops user with given id": "Degradar o usuario con esa ID", + "Deops user with given id": "Degradar á usuaria con ese ID", "Seen by %(displayName)s (%(userName)s) at %(dateTime)s": "Visto por %(displayName)s(%(userName)s en %(dateTime)s", "Code": "Código", "Unable to join community": "Non te puideches unir a comunidade", "Unable to leave community": "Non se puido deixar a comunidade", - "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.": "Os cambios realizados a súa comunidade name e avatar poida que non os vexan outros usuarios ate dentro de 30 minutos.", + "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.": "Os cambios realizados á túa comunidade nome e avatar poida que non os vexan outras usuarias ate dentro de 30 minutos.", "Join this community": "Únete a esta comunidade", "Leave this community": "Deixar esta comunidade", "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "Se enviaches un informe de fallo a través de GitHub, os informes poden axudarnos a examinar o problema. Os informes de fallo conteñen datos do uso da aplicación incluíndo o teu nome de usuaria, os IDs ou alcumes das salas e grupos que visitaches e os nomes de usuaria de outras persoas. Non conteñen mensaxes.", @@ -778,7 +778,7 @@ "Riot uses many advanced browser features, some of which are not available or experimental in your current browser.": "Riot utiliza características avanzadas do navegador, algunhas das cales non están dispoñibles ou son experimentais no seu navegador actual.", "Developer Tools": "Ferramentas para desenvolver", "Preparing to send logs": "Preparándose para enviar informe", - "Remember, you can always set an email address in user settings if you change your mind.": "Lembre que sempre poderá poñer un enderezo de correo nos axustes de usuario se cambiase de idea.", + "Remember, you can always set an email address in user settings if you change your mind.": "Lembra que sempre poderás poñer un enderezo de email nos axustes de usuaria se cambiases de idea.", "Explore Account Data": "Ollar datos da conta", "All messages (noisy)": "Todas as mensaxes (alto)", "Saturday": "Sábado", @@ -819,7 +819,7 @@ "Back": "Atrás", "Reply": "Resposta", "Show message in desktop notification": "Mostrar mensaxe nas notificacións de escritorio", - "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "Os informes de depuración conteñen datos de utilización do aplicativo como o seu nome de usuario, os IDs ou alcumes de salas e grupos que vostede visitou e os nomes de usuarios doutras usuarias. Non conteñen mensaxes.", + "Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "Os informes de depuración conteñen datos de utilización da aplicación como o teu nome de usuaria, os IDs ou alias de salas e grupos que visitachese os nomes de usuaria doutras usuarias. Non conteñen mensaxes.", "Unhide Preview": "Desagochar a vista previa", "Unable to join network": "Non se puido conectar ca rede", "You might have configured them in a client other than Riot. You cannot tune them in Riot but they still apply": "Pode que os configurases nun cliente diferente de Riot. Non podes establecelos desde Riot pero aínda así aplicaranse", @@ -845,7 +845,7 @@ "View Source": "Ver fonte", "Event Content": "Contido do evento", "Thank you!": "Grazas!", - "With your current browser, the look and feel of the application may be completely incorrect, and some or all features may not function. If you want to try it anyway you can continue, but you are on your own in terms of any issues you may encounter!": "Co seu navegador actual a aparencia e uso do aplicativo poderían estar totalmente falseadas, e algunhas características poderían non funcionar. Se quere pode continuar, pero debe ser consciente de que poden haber fallos!", + "With your current browser, the look and feel of the application may be completely incorrect, and some or all features may not function. If you want to try it anyway you can continue, but you are on your own in terms of any issues you may encounter!": "Co teu navegador actual a aparencia e uso da aplicación poderían estar totalmente falseadas, e algunhas características poderían non funcionar. Se queres podes continuar, pero debes ser consciente de que pode haber fallos!", "Checking for an update...": "Comprobando as actualizacións...", "There are advanced notifications which are not shown here": "Existen notificacións avanzadas que non se mostran aquí", "Every page you use in the app": "Cada páxina que use na aplicación", @@ -867,18 +867,18 @@ "Enable widget screenshots on supported widgets": "Activar as capturas de trebellos para aqueles que as permiten", "Share Link to User": "Compartir a ligazón coa usuaria", "Share room": "Compartir sala", - "Muted Users": "Usuarios silenciados", + "Muted Users": "Usuarias silenciadas", "Please help improve Riot.im by sending anonymous usage data. This will use a cookie (please see our Cookie Policy).": "Axuda a mellorar Riot.im enviando os datos anónimos de uso. Usaremos unha cookie (le aquí a nosa Política de Cookies).", "Please help improve Riot.im by sending anonymous usage data. This will use a cookie.": "Axuda a mellorar Riot.im enviando datos anónimos de uso. Esto usará unha cookie.", "Yes, I want to help!": "Si, quero axudar!", - "This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. This action is irreversible.": "Iso fará que a súa deixe de ter uso de xeito permanente. Non poderá acceder e ninguén vai a poder volver a rexistrar esa mesma ID de usuario. Suporá que saía de todas as salas de conversas nas que estaba e eliminará os detalles da súa conta do servidores de identificación.Isto non se poderá desfacer", + "This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. This action is irreversible.": "Iso fará que a túa deixe de ter uso de xeito permanente. Non poderás acceder e ninguén vai a poder volver a rexistrar esa mesma ID de usuaria. Suporá que sairás de todalas salas de conversas nas que estabas e eliminarás os detalles da túa conta do servidores de identidade. Esta acción non ten volta", "Deactivating your account does not by default cause us to forget messages you have sent. If you would like us to forget your messages, please tick the box below.": "Desactivando a súa conta non supón que por defecto esquezamos as súas mensaxes enviadas. Se quere que nos esquezamos das súas mensaxes, prema na caixa de embaixo.", "To continue, please enter your password:": "Para continuar introduza o seu contrasinal:", - "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.": "A visibilidade das mensaxes en Matrix é parecida ás dos correos electrónicos. Que esquezamos as súas mensaxes significa que as súas mensaxes non se van a compartir con ningún novo membro ou usuario que non estea rexistrado. Mais aqueles usuarios que xa tiveron acceso a estas mensaxes si que seguirán tendo acceso as súas propias copias desas mensaxes.", + "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.": "A visibilidade das mensaxes en Matrix é parecida á dos correos electrónicos. Que esquezamos as túas mensaxes significa que as mensaxes non se van a compartir con ningún novo membro ou usuaria que non estea rexistrada. Mais aqueles usuarias que xa tiveron acceso a estas mensaxes si que seguirán tendo acceso as súas propias copias desas mensaxes.", "Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)": "Esquezan todas as mensaxes que eu enviara no momento en que elimine a miña conta. (Aviso: iso suporá que os seguintes participantes só verán unha versión incompleta das conversas.)", "Share Room": "Compartir sala", "Link to most recent message": "Ligazón ás mensaxes máis recentes", - "Share User": "Compartir usuario", + "Share User": "Compartir usuaria", "Share Community": "Compartir comunidade", "Share Room Message": "Compartir unha mensaxe da sala", "Link to selected message": "Ligazón á mensaxe escollida", @@ -893,10 +893,10 @@ "Audio Output": "Saída de audio", "Call in Progress": "Chamada en progreso", "A call is already in progress!": "Xa hai unha chamada en progreso!", - "Permission Required": "Precísase de permisos", - "You do not have permission to start a conference call in this room": "Non ten permisos para comezar unha chamada de conferencia nesta sala", + "Permission Required": "Precísanse permisos", + "You do not have permission to start a conference call in this room": "Non tes permisos para comezar unha chamada de conferencia nesta sala", "This event could not be displayed": "Non se puido amosar este evento", - "Demote yourself?": "Baixarse a si mesmo de rango?", + "Demote yourself?": "Baixarse a ti mesma de rango?", "Demote": "Baixar de rango", "In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.": "Nas salas cifradas, como é esta, está desactivado por defecto a previsualización das URL co fin de asegurarse de que o servidor local (que é onde se gardan as previsualizacións) non poida recoller información sobre das ligazóns que se ven nesta sala.", "When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.": "Cando alguén pon unha URL na mensaxe, esta previsualízarase para que así se coñezan xa cousas delas como o título, a descrición ou as imaxes que inclúe ese sitio web.", @@ -963,7 +963,7 @@ "Sign in and regain access to your account.": "Conéctate e recupera o acceso a túa conta.", "You cannot sign in to your account. Please contact your homeserver admin for more information.": "Non podes conectar a conta. Contacta coa administración do teu servidor para máis información.", "Warning: Your personal data (including encryption keys) is still stored in this session. Clear it if you're finished using this session, or want to sign in to another account.": "Aviso: os teus datos personais (incluíndo chaves de cifrado) aínda están gardadas nesta sesión. Pechaa se remataches de usar esta sesión, ou se quere conectar con outra conta.", - "Unable to load! Check your network connectivity and try again.": "Non cargou! Comproba a conexión a rede e volta a intentalo.", + "Unable to load! Check your network connectivity and try again.": "Non cargou! Comproba a conexión á rede e volta a intentalo.", "There are unknown sessions in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call.": "Hai sesións descoñecidas nesta sala: se continúas sen verificalas será posible para alguén fisgar na túa chamada.", "Review Sessions": "Revisar Sesións", "Call failed due to misconfigured server": "Fallou a chamada porque o servidor está mal configurado", @@ -2370,5 +2370,59 @@ "Esc": "Esc", "Enter": "Intro", "Space": "Espazo", - "End": "Fin" + "End": "Fin", + "You joined the call": "Unícheste á chamada", + "%(senderName)s joined the call": "%(senderName)s uniuse á chamada", + "Call in progress": "Chamada en curso", + "You left the call": "Deixáchela chamada", + "%(senderName)s left the call": "%(senderName)s deixou a chamada", + "Call ended": "Chamada rematada", + "You started a call": "Iniciaches unha chamada", + "%(senderName)s started a call": "%(senderName)s iniciou unha chamada", + "Waiting for answer": "Agardando resposta", + "%(senderName)s is calling": "%(senderName)s está chamando", + "You created the room": "Creaches a sala", + "%(senderName)s created the room": "%(senderName)s creou a sala", + "You made the chat encrypted": "Cifraches a conversa", + "%(senderName)s made the chat encrypted": "%(senderName)s cifrou a conversa", + "You made history visible to new members": "Fixeches visible o historial para novos membros", + "%(senderName)s made history visible to new members": "%(senderName)s fixo o historial visible para novos membros", + "You made history visible to anyone": "Fixeches que o historial sexa visible para todas", + "%(senderName)s made history visible to anyone": "%(senderName)s fixo o historial visible para todas", + "You made history visible to future members": "Fixeches o historial visible para membros futuros", + "%(senderName)s made history visible to future members": "%(senderName)s fixo o historial visible para futuros membros", + "You were invited": "Foches convidada", + "%(targetName)s was invited": "%(targetName)s foi convidada", + "You left": "Saíches", + "%(targetName)s left": "%(targetName)s saíu", + "You were kicked (%(reason)s)": "Expulsáronte (%(reason)s)", + "%(targetName)s was kicked (%(reason)s)": "%(targetName)s foi expulsada (%(reason)s)", + "You were kicked": "Foches expulsada", + "%(targetName)s was kicked": "%(targetName)s foi expulsada", + "You rejected the invite": "Rexeitaches o convite", + "%(targetName)s rejected the invite": "%(targetName)s rexeitou o convite", + "You were uninvited": "Retiraronche o convite", + "%(targetName)s was uninvited": "Retirouse o convite para %(targetName)s", + "You were banned (%(reason)s)": "Foches bloqueada (%(reason)s)", + "%(targetName)s was banned (%(reason)s)": "%(targetName)s foi bloqueada (%(reason)s)", + "You were banned": "Foches bloqueada", + "%(targetName)s was banned": "%(targetName)s foi bloqueada", + "You joined": "Unícheste", + "%(targetName)s joined": "%(targetName)s uneuse", + "You changed your name": "Cambiaches o nome", + "%(targetName)s changed their name": "%(targetName)s cambiou o seu nome", + "You changed your avatar": "Cambiáchelo avatar", + "%(targetName)s changed their avatar": "%(targetName)s cambiou o seu avatar", + "%(senderName)s %(emote)s": "%(senderName)s %(emote)s", + "%(senderName)s: %(message)s": "%(senderName)s: %(message)s", + "You changed the room name": "Cambiaches o nome da sala", + "%(senderName)s changed the room name": "%(senderName)s cambiou o nome da sala", + "%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s", + "%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s", + "You uninvited %(targetName)s": "Retiraches o convite para %(targetName)s", + "%(senderName)s uninvited %(targetName)s": "%(senderName)s retiroulle o convite a %(targetName)s", + "You invited %(targetName)s": "Convidaches a %(targetName)s", + "%(senderName)s invited %(targetName)s": "%(senderName)s convidou a %(targetName)s", + "You changed the room topic": "Cambiaches o tema da sala", + "%(senderName)s changed the room topic": "%(senderName)s cambiou o asunto da sala" } From 554af62aade59b457f0cc5c634f6f208830044d1 Mon Sep 17 00:00:00 2001 From: Lizzy Date: Fri, 26 Jun 2020 19:53:11 +0000 Subject: [PATCH 170/294] Translated using Weblate (Spanish) Currently translated at 89.0% (2087 of 2344 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/es/ --- src/i18n/strings/es.json | 43 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/es.json b/src/i18n/strings/es.json index 2b3cb7b1b9..33443bd5bd 100644 --- a/src/i18n/strings/es.json +++ b/src/i18n/strings/es.json @@ -1179,7 +1179,7 @@ "Use a longer keyboard pattern with more turns": "Usa un patrón de tecleo largo con más vueltas", "Enable Community Filter Panel": "Habilitar el Panel de Filtro de Comunidad", "Verify this user by confirming the following emoji appear on their screen.": "Verifica este usuario confirmando que los siguientes emojis aparecen en su pantalla.", - "Your Riot is misconfigured": "Tu Riot está mal configurado", + "Your Riot is misconfigured": "Tu Riot tiene un error de configuración", "Whether or not you're logged in (we don't record your username)": "Hayas o no iniciado sesión (no guardamos tu nombre de usuario)", "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "Uses o no los 'breadcrumbs' (iconos sobre la lista de salas)", "A conference call could not be started because the integrations server is not available": "No se pudo iniciar la conferencia porque el servidor de integraciones no está disponible", @@ -2191,5 +2191,44 @@ "This homeserver does not support login using email address.": "Este servidor doméstico no admite iniciar sesión con una dirección de correo electrónico.", "This account has been deactivated.": "Esta cuenta ha sido desactivada.", "Room name or address": "Nombre o dirección de la sala", - "Address (optional)": "Dirección (opcional)" + "Address (optional)": "Dirección (opcional)", + "Help us improve Riot": "Ayúdanos a mejorar Riot", + "Send anonymous usage data which helps us improve Riot. This will use a cookie.": "Enviar información anónima de uso nos ayudaría bastante a mejorar Riot. Esto cuenta como utilizar una cookie.", + "I want to help": "Quiero ayudar", + "Ok": "Ok", + "Set password": "Establecer contraseña", + "To return to your account in future you need to set a password": "Para poder regresar a tu cuenta en un futuro necesitas establecer una contraseña", + "Restart": "Reiniciar", + "Upgrade your Riot": "Actualiza tu Riot", + "A new version of Riot is available!": "¡Una nueva versión de Riot se encuentra disponible!", + "You joined the call": "Te has unido a la llamada", + "%(senderName)s joined the call": "%(senderName)s se ha unido a la llamada", + "Call in progress": "Llamada en progreso", + "You left the call": "Has abandonado la llamada", + "%(senderName)s left the call": "%(senderName)s dejo la llamada", + "Call ended": "La llamada ha finalizado", + "You started a call": "Has iniciado una llamada", + "%(senderName)s started a call": "%(senderName)s inicio una llamada", + "Waiting for answer": "Esperado por una respuesta", + "%(senderName)s is calling": "%(senderName)s está llamando", + "%(senderName)s created the room": "%(senderName)s creo la sala", + "You were invited": "Has sido invitado", + "%(targetName)s was invited": "%(targetName)s ha sido invitado", + "%(targetName)s left": "%(targetName)s se ha ido", + "You were kicked (%(reason)s)": "Has sido expulsado por %(reason)s", + "You rejected the invite": "Has rechazado la invitación", + "%(targetName)s rejected the invite": "%(targetName)s rechazo la invitación", + "You were banned (%(reason)s)": "Has sido baneado por %(reason)s", + "%(targetName)s was banned (%(reason)s)": "%(targetName)s fue baneado por %(reason)s", + "You were banned": "Has sido baneado", + "%(targetName)s was banned": "%(targetName)s fue baneado", + "You joined": "Te has unido", + "%(targetName)s joined": "%(targetName)s se ha unido", + "You changed your name": "Has cambiado tu nombre", + "%(targetName)s changed their name": "%(targetName)s cambio su nombre", + "You changed your avatar": "Ha cambiado su avatar", + "%(targetName)s changed their avatar": "%(targetName)s ha cambiado su avatar", + "You changed the room name": "Has cambiado el nombre de la sala", + "%(senderName)s changed the room name": "%(senderName)s cambio el nombre de la sala", + "You invited %(targetName)s": "Has invitado a %(targetName)s" } From 753f7aa5b8d63c7fee85d127fd39b98f3969cebd Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 27 Jun 2020 22:48:54 -0600 Subject: [PATCH 171/294] Remove the DM button from new room tiles Fixes https://github.com/vector-im/riot-web/issues/14221 --- res/css/views/rooms/_RoomTile2.scss | 4 ---- src/components/views/rooms/RoomTile2.tsx | 13 ++----------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/res/css/views/rooms/_RoomTile2.scss b/res/css/views/rooms/_RoomTile2.scss index a97d1fd5b9..2b659a0c8e 100644 --- a/res/css/views/rooms/_RoomTile2.scss +++ b/res/css/views/rooms/_RoomTile2.scss @@ -193,10 +193,6 @@ limitations under the License. mask-image: url('$(res)/img/feather-customised/arrow-down.svg'); } - .mx_RoomTile2_iconUser::before { - mask-image: url('$(res)/img/feather-customised/user.svg'); - } - .mx_RoomTile2_iconSettings::before { mask-image: url('$(res)/img/feather-customised/settings.svg'); } diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index 3d0a555877..2791bd9730 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -135,11 +135,8 @@ export default class RoomTile2 extends React.Component { ev.preventDefault(); ev.stopPropagation(); - if (tagId === DefaultTagID.DM) { - // TODO: DM Flagging - } else { - // TODO: XOR favourites and low priority - } + // TODO: Support tagging: https://github.com/vector-im/riot-web/issues/14211 + // TODO: XOR favourites and low priority: https://github.com/vector-im/riot-web/issues/14210 }; private onLeaveRoomClick = (ev: ButtonEvent) => { @@ -196,12 +193,6 @@ export default class RoomTile2 extends React.Component { {_t("Low Priority")} -
        • - this.onTagRoom(e, DefaultTagID.DM)}> - - {_t("Direct Chat")} - -
        • From ee2c216c4df97f6d8867cee7b58ee84ac93510f4 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sun, 28 Jun 2020 20:03:04 -0600 Subject: [PATCH 172/294] Clean up TODO comments for new room list All relevant TODOs should still be present, and reference an issue for easy finding. --- res/css/structures/_LeftPanel2.scss | 2 +- res/css/views/rooms/_RoomBreadcrumbs2.scss | 2 ++ res/css/views/rooms/_RoomSublist2.scss | 2 +- res/css/views/rooms/_RoomTile2.scss | 5 +-- src/components/structures/LeftPanel2.tsx | 25 +++++---------- src/components/structures/RoomSearch.tsx | 2 ++ src/components/structures/UserMenu.tsx | 2 +- .../views/rooms/NotificationBadge.tsx | 5 ++- .../views/rooms/RoomBreadcrumbs2.tsx | 9 ++++-- src/components/views/rooms/RoomList2.tsx | 9 ++++-- src/components/views/rooms/RoomSublist2.tsx | 31 ++++++------------- src/components/views/rooms/RoomTile2.tsx | 29 +++++------------ src/stores/BreadcrumbsStore.ts | 6 ++-- src/stores/room-list/ListLayout.ts | 3 +- src/stores/room-list/MessagePreviewStore.ts | 2 +- src/stores/room-list/RoomListStore2.ts | 31 +++++++++++++++---- .../room-list/RoomListStoreTempProxy.ts | 2 +- src/stores/room-list/TagWatcher.ts | 5 +-- src/stores/room-list/algorithms/Algorithm.ts | 23 +++++++++++--- .../list-ordering/ImportanceAlgorithm.ts | 4 ++- .../list-ordering/NaturalAlgorithm.ts | 4 ++- .../algorithms/tag-sorting/RecentAlgorithm.ts | 1 + .../filters/CommunityFilterCondition.ts | 1 + .../room-list/filters/NameFilterCondition.ts | 3 +- src/stores/room-list/membership.ts | 2 +- 25 files changed, 117 insertions(+), 93 deletions(-) diff --git a/res/css/structures/_LeftPanel2.scss b/res/css/structures/_LeftPanel2.scss index 67fa9ba557..40babaa9ca 100644 --- a/res/css/structures/_LeftPanel2.scss +++ b/res/css/structures/_LeftPanel2.scss @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// TODO: Rename to mx_LeftPanel during replacement of old component +// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231 $tagPanelWidth: 70px; // only applies in this file, used for calculations diff --git a/res/css/views/rooms/_RoomBreadcrumbs2.scss b/res/css/views/rooms/_RoomBreadcrumbs2.scss index ac5a9fc34e..dd9581069c 100644 --- a/res/css/views/rooms/_RoomBreadcrumbs2.scss +++ b/res/css/views/rooms/_RoomBreadcrumbs2.scss @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231 + .mx_RoomBreadcrumbs2 { width: 100%; diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss index f859aba623..ffb96cf600 100644 --- a/res/css/views/rooms/_RoomSublist2.scss +++ b/res/css/views/rooms/_RoomSublist2.scss @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// TODO: Rename to mx_RoomSublist during replacement of old component +// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231 .mx_RoomSublist2 { // The sublist is a column of rows, essentially diff --git a/res/css/views/rooms/_RoomTile2.scss b/res/css/views/rooms/_RoomTile2.scss index a97d1fd5b9..ffefd86c95 100644 --- a/res/css/views/rooms/_RoomTile2.scss +++ b/res/css/views/rooms/_RoomTile2.scss @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// TODO: Rename to mx_RoomTile during replacement of old component +// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231 // Note: the room tile expects to be in a flexbox column container .mx_RoomTile2 { @@ -96,7 +96,8 @@ limitations under the License. // TODO: [Notifications] Use mx_RoomTile2_notificationsButton, similar to the following approach: // https://github.com/matrix-org/matrix-react-sdk/blob/2180a56074f3698fc0241c309a72ba6cad802d1c/res/css/views/rooms/_RoomSublist2.scss#L48-L76 // You'll need to do the same down below on the &:hover selector for the tile. - // ... also remove this 4 line TODO comment. + // See https://github.com/vector-im/riot-web/issues/13961. + // ... also remove this 5 line TODO comment. .mx_RoomTile2_menuButton, .mx_RoomTile2_notificationsButton { width: 0; diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index 6e0faff57f..0f614435e5 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -31,6 +31,9 @@ import { UPDATE_EVENT } from "../../stores/AsyncStore"; import ResizeNotifier from "../../utils/ResizeNotifier"; import SettingsStore from "../../settings/SettingsStore"; +// 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 + /******************************************************************* * CAUTION * ******************************************************************* @@ -45,7 +48,7 @@ interface IProps { } interface IState { - searchFilter: string; // TODO: Move search into room list? + searchFilter: string; showBreadcrumbs: boolean; showTagPanel: boolean; } @@ -54,12 +57,7 @@ export default class LeftPanel2 extends React.Component { private listContainerRef: React.RefObject = createRef(); private tagPanelWatcherRef: string; - // TODO: Properly support TagPanel - // TODO: Properly support searching/filtering - // TODO: Properly support breadcrumbs - // TODO: a11y - // TODO: actually make this useful in general (match design proposals) - // TODO: Fadable support (is this still needed?) + // TODO: a11y: https://github.com/vector-im/riot-web/issues/14180 constructor(props: IProps) { super(props); @@ -138,7 +136,7 @@ export default class LeftPanel2 extends React.Component { } } - // TODO: Apply this on resize, init, etc for reliability + // TODO: Improve header reliability: https://github.com/vector-im/riot-web/issues/14232 private onScroll = (ev: React.MouseEvent) => { const list = ev.target as HTMLDivElement; this.handleStickyHeaders(list); @@ -150,11 +148,6 @@ export default class LeftPanel2 extends React.Component { }; private renderHeader(): React.ReactNode { - // TODO: Update when profile info changes - // TODO: Presence - // TODO: Breadcrumbs toggle - // TODO: Menu button - let breadcrumbs; if (this.state.showBreadcrumbs) { breadcrumbs = ( @@ -173,8 +166,6 @@ export default class LeftPanel2 extends React.Component { } private renderSearchExplore(): React.ReactNode { - // TODO: Collapsed support - return (
          @@ -195,7 +186,7 @@ export default class LeftPanel2 extends React.Component {
          ); - // TODO: Improve props for RoomList2 + // TODO: Determine what these onWhatever handlers do: https://github.com/vector-im/riot-web/issues/14180 const roomList = {/*TODO*/}} resizeNotifier={null} @@ -206,7 +197,7 @@ export default class LeftPanel2 extends React.Component { isMinimized={this.props.isMinimized} />; - // TODO: Conference handling / calls + // TODO: Conference handling / calls: https://github.com/vector-im/riot-web/issues/14177 const containerClasses = classNames({ "mx_LeftPanel2": true, diff --git a/src/components/structures/RoomSearch.tsx b/src/components/structures/RoomSearch.tsx index 345cf83d31..8e64353954 100644 --- a/src/components/structures/RoomSearch.tsx +++ b/src/components/structures/RoomSearch.tsx @@ -25,6 +25,8 @@ import { Key } from "../../Keyboard"; import AccessibleButton from "../views/elements/AccessibleButton"; import { Action } from "../../dispatcher/actions"; +// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14231 + /******************************************************************* * CAUTION * ******************************************************************* diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 19e57ac51b..8c06a06852 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -136,7 +136,7 @@ export default class UserMenu extends React.Component { ev.preventDefault(); ev.stopPropagation(); - // TODO: Archived room view (deferred) + // TODO: Archived room view: https://github.com/vector-im/riot-web/issues/14038 console.log("TODO: Show archived rooms"); }; diff --git a/src/components/views/rooms/NotificationBadge.tsx b/src/components/views/rooms/NotificationBadge.tsx index 6929341845..2111310555 100644 --- a/src/components/views/rooms/NotificationBadge.tsx +++ b/src/components/views/rooms/NotificationBadge.tsx @@ -35,7 +35,8 @@ export const NOTIFICATION_STATE_UPDATE = "update"; export enum NotificationColor { // Inverted (None -> Red) because we do integer comparisons on this None, // nothing special - Bold, // no badge, show as unread // TODO: This goes away with new notification structures + // TODO: Remove bold with notifications: https://github.com/vector-im/riot-web/issues/14227 + Bold, // no badge, show as unread Grey, // unread notified messages Red, // unread pings } @@ -141,6 +142,8 @@ export default class NotificationBadge extends React.PureComponent { return ( { for (const orderedTagId of TAG_ORDER) { if (COMMUNITY_TAGS_BEFORE_TAG === orderedTagId) { // Populate community invites if we have the chance - // TODO + // TODO: Community invites: https://github.com/vector-im/riot-web/issues/14179 } if (CUSTOM_TAGS_BEFORE_TAG === orderedTagId) { // Populate custom tags if needed - // TODO + // TODO: Custom tags: https://github.com/vector-im/riot-web/issues/14091 } const orderedRooms = this.state.sublists[orderedTagId] || []; diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index d34ddf090d..58ebf54bf7 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -34,6 +34,9 @@ import RoomListStore from "../../../stores/room-list/RoomListStore2"; import { ListAlgorithm, SortAlgorithm } from "../../../stores/room-list/algorithms/models"; import { TagID } from "../../../stores/room-list/models"; +// 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 + /******************************************************************* * CAUTION * ******************************************************************* @@ -59,12 +62,7 @@ interface IProps { isMinimized: boolean; tagId: TagID; - // TODO: Collapsed state - // TODO: Group invites - // TODO: Calls - // TODO: forceExpand? - // TODO: Header clicking - // TODO: Spinner support for historical + // TODO: Account for https://github.com/vector-im/riot-web/issues/14179 } interface IState { @@ -89,7 +87,7 @@ export default class RoomSublist2 extends React.Component { } private get numTiles(): number { - // TODO: Account for group invites + // TODO: Account for group invites: https://github.com/vector-im/riot-web/issues/14179 return (this.props.rooms || []).length; } @@ -268,17 +266,12 @@ export default class RoomSublist2 extends React.Component { } private renderHeader(): React.ReactElement { - // TODO: Title on collapsed - // TODO: Incoming call box - return ( {({onFocus, isActive, ref}) => { - // TODO: Use onFocus + // TODO: Use onFocus: https://github.com/vector-im/riot-web/issues/14180 const tabIndex = isActive ? 0 : -1; - // TODO: Collapsed state - const badge = ; let addRoomButton = null; @@ -309,7 +302,7 @@ export default class RoomSublist2 extends React.Component {
        • ); - // TODO: a11y (see old component) + // TODO: a11y (see old component): https://github.com/vector-im/riot-web/issues/14180 // Note: the addRoomButton conditionally gets moved around // the DOM depending on whether or not the list is minimized. // If we're minimized, we want it below the header so it @@ -343,15 +336,12 @@ export default class RoomSublist2 extends React.Component { } public render(): React.ReactElement { - // TODO: Proper rendering - // TODO: Error boundary + // TODO: Error boundary: https://github.com/vector-im/riot-web/issues/14185 const tiles = this.renderTiles(); const classes = classNames({ - // TODO: Proper collapse support 'mx_RoomSublist2': true, - 'mx_RoomSublist2_collapsed': false, // len && isCollapsed 'mx_RoomSublist2_hasMenuOpen': this.state.menuDisplayed, 'mx_RoomSublist2_minimized': this.props.isMinimized, }); @@ -360,9 +350,6 @@ export default class RoomSublist2 extends React.Component { if (tiles.length > 0) { const layout = this.props.layout; // to shorten calls - // TODO: Lazy list rendering - // TODO: Whatever scrolling magic needs to happen here - const nVisible = Math.floor(layout.visibleTiles); const visibleTiles = tiles.slice(0, nVisible); @@ -457,7 +444,7 @@ export default class RoomSublist2 extends React.Component { ); } - // TODO: onKeyDown support + // TODO: onKeyDown support: https://github.com/vector-im/riot-web/issues/14180 return (
          { private roomTileRef: React.RefObject = createRef(); private generalMenuButtonRef: React.RefObject = createRef(); - // TODO: Custom status - // TODO: Lock icon - // TODO: Presence indicator - // TODO: e2e shields - // TODO: Handle changes to room aesthetics (name, join rules, etc) - // TODO: scrollIntoView? - // TODO: hover, badge, etc - // TODO: isSelected for hover effects - // TODO: Context menu - // TODO: a11y + // TODO: a11y: https://github.com/vector-im/riot-web/issues/14180 constructor(props: IProps) { super(props); @@ -108,7 +100,7 @@ export default class RoomTile2 extends React.Component { private onTileClick = (ev: React.KeyboardEvent) => { dis.dispatch({ action: 'view_room', - // TODO: Support show_room_tile in new room list + // TODO: Support show_room_tile in new room list: https://github.com/vector-im/riot-web/issues/14233 show_room_tile: true, // make sure the room is visible in the list room_id: this.props.room.roomId, clear_search: (ev && (ev.key === Key.ENTER || ev.key === Key.SPACE)), @@ -240,10 +232,8 @@ export default class RoomTile2 extends React.Component { } public render(): React.ReactElement { - // TODO: Collapsed state - // TODO: Invites - // TODO: a11y proper - // TODO: Render more than bare minimum + // TODO: Invites: https://github.com/vector-im/riot-web/issues/14198 + // TODO: a11y proper: https://github.com/vector-im/riot-web/issues/14180 const classes = classNames({ 'mx_RoomTile2': true, @@ -265,9 +255,6 @@ export default class RoomTile2 extends React.Component { if (typeof name !== 'string') name = ''; name = name.replace(":", ":\u200b"); // add a zero-width space to allow linewrapping after the colon - // TODO: Support collapsed state properly - // TODO: Tooltip? - let messagePreview = null; if (this.props.showMessagePreview && !this.props.isMinimized) { // The preview store heavily caches this info, so should be safe to hammer. diff --git a/src/stores/BreadcrumbsStore.ts b/src/stores/BreadcrumbsStore.ts index 332fa7fe2e..1c47075cbb 100644 --- a/src/stores/BreadcrumbsStore.ts +++ b/src/stores/BreadcrumbsStore.ts @@ -57,7 +57,7 @@ export class BreadcrumbsStore extends AsyncStoreWithClient { protected async onAction(payload: ActionPayload) { if (!this.matrixClient) return; - // TODO: Remove when new room list is made the default + // TODO: Remove when new room list is made the default: https://github.com/vector-im/riot-web/issues/14231 if (!RoomListStoreTempProxy.isUsingNewStore()) return; if (payload.action === 'setting_updated') { @@ -80,7 +80,7 @@ export class BreadcrumbsStore extends AsyncStoreWithClient { } protected async onReady() { - // TODO: Remove when new room list is made the default + // TODO: Remove when new room list is made the default: https://github.com/vector-im/riot-web/issues/14231 if (!RoomListStoreTempProxy.isUsingNewStore()) return; await this.updateRooms(); @@ -91,7 +91,7 @@ export class BreadcrumbsStore extends AsyncStoreWithClient { } protected async onNotReady() { - // TODO: Remove when new room list is made the default + // TODO: Remove when new room list is made the default: https://github.com/vector-im/riot-web/issues/14231 if (!RoomListStoreTempProxy.isUsingNewStore()) return; this.matrixClient.removeListener("Room.myMembership", this.onMyMembership); diff --git a/src/stores/room-list/ListLayout.ts b/src/stores/room-list/ListLayout.ts index 8ca8ad637b..56f94ccd9a 100644 --- a/src/stores/room-list/ListLayout.ts +++ b/src/stores/room-list/ListLayout.ts @@ -85,7 +85,8 @@ export class ListLayout { } public get defaultVisibleTiles(): number { - // TODO: Remove dogfood flag + // TODO: Remove dogfood flag: https://github.com/vector-im/riot-web/issues/14231 + // TODO: Resolve dogfooding: https://github.com/vector-im/riot-web/issues/14137 const val = Number(localStorage.getItem("mx_dogfood_rl_defTiles") || 4); return val + RESIZER_BOX_FACTOR; } diff --git a/src/stores/room-list/MessagePreviewStore.ts b/src/stores/room-list/MessagePreviewStore.ts index b727069f9f..01ddde2e17 100644 --- a/src/stores/room-list/MessagePreviewStore.ts +++ b/src/stores/room-list/MessagePreviewStore.ts @@ -192,7 +192,7 @@ export class MessagePreviewStore extends AsyncStoreWithClient { protected async onAction(payload: ActionPayload) { if (!this.matrixClient) return; - // TODO: Remove when new room list is made the default + // TODO: Remove when new room list is made the default: https://github.com/vector-im/riot-web/issues/14231 if (!RoomListStoreTempProxy.isUsingNewStore()) return; if (payload.action === 'MatrixActions.Room.timeline' || payload.action === 'MatrixActions.Event.decrypted') { diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index 99eee82d4e..58a78f4dd8 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -72,7 +72,7 @@ export class RoomListStore2 extends AsyncStore { return this._matrixClient; } - // TODO: Remove enabled flag when the old RoomListStore goes away + // TODO: Remove enabled flag with the old RoomListStore: https://github.com/vector-im/riot-web/issues/14231 private checkEnabled() { this.enabled = SettingsStore.isFeatureEnabled("feature_new_room_list"); if (this.enabled) { @@ -89,7 +89,7 @@ export class RoomListStore2 extends AsyncStore { } private onRVSUpdate = () => { - if (!this.enabled) return; // TODO: Remove enabled flag when RoomListStore2 takes over + if (!this.enabled) return; // TODO: Remove with https://github.com/vector-im/riot-web/issues/14231 if (!this.matrixClient) return; // We assume there won't be RVS updates without a client const activeRoomId = RoomViewStore.getRoomId(); @@ -99,6 +99,7 @@ export class RoomListStore2 extends AsyncStore { const activeRoom = this.matrixClient.getRoom(activeRoomId); if (!activeRoom) throw new Error(`${activeRoomId} is current in RVS but missing from client`); 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; } @@ -112,7 +113,7 @@ export class RoomListStore2 extends AsyncStore { return; } - // TODO: Remove this once the RoomListStore becomes default + // TODO: Remove with https://github.com/vector-im/riot-web/issues/14231 this.checkEnabled(); if (!this.enabled) return; @@ -163,12 +164,14 @@ export class RoomListStore2 extends AsyncStore { console.warn(`Own read receipt was in unknown room ${room.roomId}`); return; } + // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 console.log(`[RoomListDebug] Got own read receipt in ${room.roomId}`); await this.handleRoomUpdate(room, RoomUpdateCause.ReadReceipt); return; } } else if (payload.action === 'MatrixActions.Room.tags') { const roomPayload = (payload); // TODO: Type out the dispatcher types + // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 console.log(`[RoomListDebug] Got tag change in ${roomPayload.room.roomId}`); await this.handleRoomUpdate(roomPayload.room, RoomUpdateCause.PossibleTagChange); } else if (payload.action === 'MatrixActions.Room.timeline') { @@ -180,10 +183,12 @@ export class RoomListStore2 extends AsyncStore { const roomId = eventPayload.event.getRoomId(); const room = this.matrixClient.getRoom(roomId); const tryUpdate = async (updatedRoom: Room) => { + // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 console.log(`[RoomListDebug] Live timeline event ${eventPayload.event.getId()} in ${updatedRoom.roomId}`); if (eventPayload.event.getType() === 'm.room.tombstone' && eventPayload.event.getStateKey() === '') { + // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 console.log(`[RoomListDebug] Got tombstone event - regenerating room list`); - // TODO: We could probably be smarter about this + // TODO: We could probably be smarter about this: https://github.com/vector-im/riot-web/issues/14035 await this.regenerateAllLists(); return; // don't pass the update down - we will have already handled it in the regen } @@ -208,13 +213,15 @@ export class RoomListStore2 extends AsyncStore { console.warn(`Event ${eventPayload.event.getId()} was decrypted in an unknown room ${roomId}`); return; } + // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 console.log(`[RoomListDebug] Decrypted timeline event ${eventPayload.event.getId()} in ${roomId}`); - // TODO: Check that e2e rooms are calculated correctly on initial load. + // TODO: Verify that e2e rooms are handled on init: https://github.com/vector-im/riot-web/issues/14238 // It seems like when viewing the room the timeline is decrypted, rather than at startup. This could // cause inaccuracies with the list ordering. We may have to decrypt the last N messages of every room :( await this.handleRoomUpdate(room, RoomUpdateCause.Timeline); } else if (payload.action === 'MatrixActions.accountData' && payload.event_type === 'm.direct') { const eventPayload = (payload); // TODO: Type out the dispatcher types + // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 console.log(`[RoomListDebug] Received updated DM map`); const dmMap = eventPayload.event.getContent(); for (const userId of Object.keys(dmMap)) { @@ -236,6 +243,7 @@ export class RoomListStore2 extends AsyncStore { } else if (payload.action === 'MatrixActions.Room.myMembership') { const membershipPayload = (payload); // TODO: Type out the dispatcher types if (membershipPayload.oldMembership !== "join" && membershipPayload.membership === "join") { + // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 console.log(`[RoomListDebug] Handling new room ${membershipPayload.room.roomId}`); await this.algorithm.handleRoomUpdate(membershipPayload.room, RoomUpdateCause.NewRoom); return; @@ -243,6 +251,7 @@ export class RoomListStore2 extends AsyncStore { // If it's not a join, it's transitioning into a different list (possibly historical) if (membershipPayload.oldMembership !== membershipPayload.membership) { + // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 console.log(`[RoomListDebug] Handling membership change in ${membershipPayload.room.roomId}`); await this.algorithm.handleRoomUpdate(membershipPayload.room, RoomUpdateCause.PossibleTagChange); return; @@ -253,6 +262,7 @@ 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); } @@ -260,6 +270,7 @@ export class RoomListStore2 extends AsyncStore { public async setTagSorting(tagId: TagID, sort: SortAlgorithm) { await this.algorithm.setTagSorting(tagId, sort); + // TODO: Per-account? https://github.com/vector-im/riot-web/issues/14114 localStorage.setItem(`mx_tagSort_${tagId}`, sort); } @@ -269,11 +280,13 @@ export class RoomListStore2 extends AsyncStore { // noinspection JSMethodCanBeStatic private getStoredTagSorting(tagId: TagID): SortAlgorithm { + // TODO: Per-account? https://github.com/vector-im/riot-web/issues/14114 return localStorage.getItem(`mx_tagSort_${tagId}`); } public async setListOrder(tagId: TagID, order: ListAlgorithm) { await this.algorithm.setListOrdering(tagId, order); + // TODO: Per-account? https://github.com/vector-im/riot-web/issues/14114 localStorage.setItem(`mx_listOrder_${tagId}`, order); } @@ -283,6 +296,7 @@ export class RoomListStore2 extends AsyncStore { // noinspection JSMethodCanBeStatic private getStoredListOrder(tagId: TagID): ListAlgorithm { + // TODO: Per-account? https://github.com/vector-im/riot-web/issues/14114 return localStorage.getItem(`mx_listOrder_${tagId}`); } @@ -319,6 +333,7 @@ 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); }; @@ -334,8 +349,10 @@ export class RoomListStore2 extends AsyncStore { } if (this.state.tagsEnabled) { - // TODO: Find a more reliable way to get tags (this doesn't work) + // TODO: Fix custom tags: https://github.com/vector-im/riot-web/issues/14091 const roomTags = TagOrderStore.getOrderedTags() || []; + + // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 console.log("rtags", roomTags); } @@ -348,6 +365,7 @@ 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) { @@ -356,6 +374,7 @@ 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) { diff --git a/src/stores/room-list/RoomListStoreTempProxy.ts b/src/stores/room-list/RoomListStoreTempProxy.ts index 0a173d53a9..86aff178ee 100644 --- a/src/stores/room-list/RoomListStoreTempProxy.ts +++ b/src/stores/room-list/RoomListStoreTempProxy.ts @@ -24,7 +24,7 @@ import { ITagMap } from "./algorithms/models"; * Temporary RoomListStore proxy. Should be replaced with RoomListStore2 when * it is available to everyone. * - * TODO: Remove this when RoomListStore gets fully replaced. + * TODO: Delete this: https://github.com/vector-im/riot-web/issues/14231 */ export class RoomListStoreTempProxy { public static isUsingNewStore(): boolean { diff --git a/src/stores/room-list/TagWatcher.ts b/src/stores/room-list/TagWatcher.ts index 22302b695d..56b6437524 100644 --- a/src/stores/room-list/TagWatcher.ts +++ b/src/stores/room-list/TagWatcher.ts @@ -23,7 +23,7 @@ import { arrayDiff, arrayHasDiff } from "../../utils/arrays"; * Watches for changes in tags/groups to manage filters on the provided RoomListStore */ export class TagWatcher { - // TODO: Support custom tags, somehow (deferred to later work - need support elsewhere) + // TODO: Support custom tags, somehow: https://github.com/vector-im/riot-web/issues/14091 private filters = new Map(); constructor(private store: RoomListStore2) { @@ -44,7 +44,7 @@ export class TagWatcher { const newFilters = new Map(); - // TODO: Support custom tags properly + // TODO: Support custom tags, somehow: https://github.com/vector-im/riot-web/issues/14091 const filterableTags = newTags.filter(t => t.startsWith("+")); for (const tag of filterableTags) { @@ -61,6 +61,7 @@ export class TagWatcher { const diff = arrayDiff(lastTags, newTags); for (const tag of diff.added) { // TODO: Remove this check when custom tags are supported (as we shouldn't be losing filters) + // Ref https://github.com/vector-im/riot-web/issues/14091 const filter = newFilters.get(tag); if (!filter) continue; diff --git a/src/stores/room-list/algorithms/Algorithm.ts b/src/stores/room-list/algorithms/Algorithm.ts index 5f7a7bd2ef..37ce7e4ba7 100644 --- a/src/stores/room-list/algorithms/Algorithm.ts +++ b/src/stores/room-list/algorithms/Algorithm.ts @@ -34,7 +34,7 @@ import { EffectiveMembership, splitRoomsByMembership } from "../membership"; import { OrderingAlgorithm } from "./list-ordering/OrderingAlgorithm"; import { getListAlgorithmInstance } from "./list-ordering"; -// TODO: Add locking support to avoid concurrent writes? +// TODO: Add locking support to avoid concurrent writes? https://github.com/vector-im/riot-web/issues/14235 /** * Fired when the Algorithm has determined a list has been updated. @@ -185,7 +185,6 @@ export class Algorithm extends EventEmitter { // the same thing it no-ops. After we're done calling the algorithm, we'll issue // a new update for ourselves. const lastStickyRoom = this._stickyRoom; - console.log(`Last sticky room:`, lastStickyRoom); this._stickyRoom = null; this.recalculateStickyRoom(); @@ -262,6 +261,8 @@ 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}`); } @@ -296,6 +297,8 @@ 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}`); } @@ -335,6 +338,7 @@ 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)) { @@ -346,6 +350,7 @@ 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 } @@ -355,6 +360,7 @@ 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); } @@ -440,10 +446,12 @@ 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); } @@ -462,8 +470,10 @@ 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; @@ -472,8 +482,10 @@ export class Algorithm extends EventEmitter { } if (!inTag) { - // TODO: Determine if DM and push there instead + // 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`); } } @@ -537,8 +549,8 @@ export class Algorithm extends EventEmitter { if (!this.algorithms) throw new Error("Not ready: no algorithms to determine tags from"); if (cause === RoomUpdateCause.PossibleTagChange) { - // TODO: Be smarter and splice rather than regen the planet. - // TODO: No-op if no change. + // TODO: Be smarter and splice rather than regen the planet. https://github.com/vector-im/riot-web/issues/14035 + // TODO: No-op if no change. https://github.com/vector-im/riot-web/issues/14035 await this.setKnownRooms(this.rooms); return true; } @@ -548,6 +560,7 @@ export class Algorithm extends EventEmitter { // as the sticky room relies on this. if (cause !== RoomUpdateCause.NewRoom && cause !== RoomUpdateCause.RoomRemoved) { if (this.stickyRoom === room) { + // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 console.warn(`[RoomListDebug] Received ${cause} update for sticky room ${room.roomId} - ignoring`); return false; } diff --git a/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts b/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts index 15fa00c302..e2a9fc1952 100644 --- a/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts +++ b/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts @@ -87,6 +87,8 @@ 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}`); } @@ -292,7 +294,7 @@ export class ImportanceAlgorithm extends OrderingAlgorithm { // "should never happen" disclaimer goes here console.warn(`!! Room list index corruption: ${lastCat} (i:${indices[lastCat]}) is greater than ${thisCat} (i:${indices[thisCat]}) - category indices are likely desynced from reality`); - // TODO: Regenerate index when this happens + // TODO: Regenerate index when this happens: https://github.com/vector-im/riot-web/issues/14234 } } } diff --git a/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts b/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts index 96a3f58d2c..79fa2ed604 100644 --- a/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts +++ b/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts @@ -28,6 +28,8 @@ 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}`); } @@ -49,7 +51,7 @@ export class NaturalAlgorithm extends OrderingAlgorithm { if (idx >= 0) this.cachedOrderedRooms.splice(idx, 1); } - // TODO: Optimize this to avoid useless operations + // TODO: Optimize this to avoid useless operations: https://github.com/vector-im/riot-web/issues/14035 // For example, we can skip updates to alphabetic (sometimes) and manually ordered tags this.cachedOrderedRooms = await sortRoomsWithAlgorithm(this.cachedOrderedRooms, this.tagId, this.sortingAlgorithm); diff --git a/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts b/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts index 4e4df6c9d6..a122ee3ae6 100644 --- a/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts +++ b/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts @@ -32,6 +32,7 @@ export class RecentAlgorithm implements IAlgorithm { // of the rooms to each other. // TODO: We could probably improve the sorting algorithm here by finding changes. + // See https://github.com/vector-im/riot-web/issues/14035 // For example, if we spent a little bit of time to determine which elements have // actually changed (probably needs to be done higher up?) then we could do an // insertion sort or similar on the limited set of changes. diff --git a/src/stores/room-list/filters/CommunityFilterCondition.ts b/src/stores/room-list/filters/CommunityFilterCondition.ts index b7ff8e686d..9f7d8daaa3 100644 --- a/src/stores/room-list/filters/CommunityFilterCondition.ts +++ b/src/stores/room-list/filters/CommunityFilterCondition.ts @@ -52,6 +52,7 @@ 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 4ac5b68596..8625cd932c 100644 --- a/src/stores/room-list/filters/NameFilterCondition.ts +++ b/src/stores/room-list/filters/NameFilterCondition.ts @@ -41,6 +41,7 @@ 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); } @@ -57,7 +58,7 @@ export class NameFilterCondition extends EventEmitter implements IFilterConditio } } - if (!room.name) return false; // should realisitically not happen: the js-sdk always calculates a name + if (!room.name) return false; // should realistically not happen: the js-sdk always calculates a name // Note: we have to match the filter with the removeHiddenChars() room name because the // function strips spaces and other characters (M becomes RN for example, in lowercase). diff --git a/src/stores/room-list/membership.ts b/src/stores/room-list/membership.ts index 3cb4bf146c..9f1c5b7b41 100644 --- a/src/stores/room-list/membership.ts +++ b/src/stores/room-list/membership.ts @@ -63,7 +63,7 @@ export function getEffectiveMembership(membership: string): EffectiveMembership if (membership === 'invite') { return EffectiveMembership.Invite; } else if (membership === 'join') { - // TODO: Do the same for knock? Update docs as needed in the enum. + // TODO: Include knocks? Update docs as needed in the enum. https://github.com/vector-im/riot-web/issues/14237 return EffectiveMembership.Join; } else { // Probably a leave, kick, or ban From 62467144ba48ac256f0c849a98f8f3c799d8ed1e Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sun, 28 Jun 2020 20:03:55 -0600 Subject: [PATCH 173/294] Update i18n --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d721979329..17943a61c7 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1220,7 +1220,6 @@ "Unread messages.": "Unread messages.", "Favourite": "Favourite", "Low Priority": "Low Priority", - "Direct Chat": "Direct Chat", "Leave Room": "Leave Room", "Room options": "Room options", "Add a topic": "Add a topic", @@ -1898,6 +1897,7 @@ "Mentions only": "Mentions only", "Leave": "Leave", "Forget": "Forget", + "Direct Chat": "Direct Chat", "Clear status": "Clear status", "Update status": "Update status", "Set status": "Set status", From badce3373038a41e254adc6b8598a90881bdd26f Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Sun, 28 Jun 2020 09:54:15 +0000 Subject: [PATCH 174/294] Translated using Weblate (Albanian) Currently translated at 99.7% (2337 of 2344 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/sq/ --- src/i18n/strings/sq.json | 58 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sq.json b/src/i18n/strings/sq.json index 2656744f93..4ea5ff25cb 100644 --- a/src/i18n/strings/sq.json +++ b/src/i18n/strings/sq.json @@ -2526,5 +2526,61 @@ "Compact": "Kompakte", "Modern": "Moderne", "Use a system font": "Përdor një palë shkronja sistemi", - "System font name": "Emër shkronjash sistemi" + "System font name": "Emër shkronjash sistemi", + "You joined the call": "U bëtë pjesë e thirrjes", + "%(senderName)s joined the call": "%(senderName)s u bë pjesë e thirrjes", + "Call in progress": "Thirrje në ecuri e sipër", + "You left the call": "E braktisët thirrjen", + "%(senderName)s left the call": "%(senderName)s e braktisi thirrjen", + "Call ended": "Thirrja përfundoi", + "You started a call": "Filluat një thirrje", + "%(senderName)s started a call": "%(senderName)s filluat një thirrje", + "Waiting for answer": "Po pritet për përgjigje", + "%(senderName)s is calling": "%(senderName)s po thërret", + "You created the room": "Krijuat dhomën", + "%(senderName)s created the room": "%(senderName)s krijoi dhomën", + "You made the chat encrypted": "E bëtë të fshehtëzuar fjalosjen", + "%(senderName)s made the chat encrypted": "%(senderName)s e bëri të fshehtëzuar fjalosjen", + "You made history visible to new members": "E bëtë historikun të dukshëm për anëtarë të rinj", + "%(senderName)s made history visible to new members": "%(senderName)s e bëri historikun të dukshëm për anëtarë të rinj", + "You made history visible to anyone": "E bëtë historikun të dukshëm për këdo", + "%(senderName)s made history visible to anyone": "%(senderName)s e bëri historikun të dukshëm për këdo", + "You made history visible to future members": "E bëtë historikun të dukshëm për anëtarë të ardhshëm", + "%(senderName)s made history visible to future members": "%(senderName)s e bëri historikun të dukshëm për anëtarë të ardhshëm", + "You were invited": "U ftuat", + "%(targetName)s was invited": "%(targetName)s u ftua", + "You left": "Dolët", + "%(targetName)s left": "%(targetName)s doli", + "You were kicked (%(reason)s)": "U përzutë (%(reason)s)", + "%(targetName)s was kicked (%(reason)s)": "%(targetName)s u përzu (%(reason)s)", + "You were kicked": "U përzutë", + "%(targetName)s was kicked": "%(targetName)s u përzu", + "You rejected the invite": "S’pranuat ftesën", + "%(targetName)s rejected the invite": "%(targetName)s s’pranoi ftesën", + "You were uninvited": "Ju shfuqizuan ftesën", + "%(targetName)s was uninvited": "%(targetName)s i shfuqizuan ftesën", + "You were banned (%(reason)s)": "U dëbuat (%(reason)s)", + "%(targetName)s was banned (%(reason)s)": "%(targetName)s u dëbua (%(reason)s)", + "You were banned": "U dëbuat", + "%(targetName)s was banned": "%(targetName)s u dëbua", + "You joined": "U bëtë pjesë", + "%(targetName)s joined": "%(targetName)s u bë pjesë", + "You changed your name": "Ndryshuat emrin", + "%(targetName)s changed their name": "%(targetName)s ndryshoi emrin e vet", + "You changed your avatar": "Ndryshuat avatarin tuaj", + "%(targetName)s changed their avatar": "%(targetName)s ndryshoi avatarin e vet", + "%(senderName)s %(emote)s": "%(senderName)s %(emote)s", + "%(senderName)s: %(message)s": "%(senderName)s: %(message)s", + "You changed the room name": "Ndryshuat emrin e dhomës", + "%(senderName)s changed the room name": "%(senderName)s ndryshoi emrin e dhomës", + "%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s", + "%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s", + "You uninvited %(targetName)s": "Shfuqizuat ftesën për %(targetName)s", + "%(senderName)s uninvited %(targetName)s": "%(senderName)s shfuqizoi ftesën për %(targetName)s", + "You invited %(targetName)s": "Ftuat %(targetName)s", + "%(senderName)s invited %(targetName)s": "%(senderName)s ftoi %(targetName)s", + "You changed the room topic": "Ndryshuat temën e dhomës", + "%(senderName)s changed the room topic": "%(senderName)s ndryshoi temën e dhomës", + "Use a more compact ‘Modern’ layout": "Përdorni një skemë ‘Modern’ më kompakte", + "The authenticity of this encrypted message can't be guaranteed on this device.": "Mirëfilltësia e këtij mesazhi të fshehtëzuar s’mund të garantohet në këtë pajisje." } From 80de67eff5a70f3635ccff276d4ec68735e8eee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Sun, 28 Jun 2020 20:36:42 +0000 Subject: [PATCH 175/294] Translated using Weblate (Estonian) Currently translated at 75.2% (1762 of 2344 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/et/ --- src/i18n/strings/et.json | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index 7f029f9328..7afcbcaf58 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -1813,5 +1813,36 @@ "Deactivate user": "Blokeeri kasutaja", "Failed to deactivate user": "Kasutaja blokeerimine ei õnnestunud", "This client does not support end-to-end encryption.": "See klient ei toeta läbivat krüptimist.", - "Security": "Turvalisus" + "Security": "Turvalisus", + "Using this widget may share data with %(widgetDomain)s & your Integration Manager.": "Selle vidina kasutamisel võidakse jagada andmeid saitidega %(widgetDomain)s ning sinu vidinahalduriga.", + "Using this widget may share data with %(widgetDomain)s.": "Selle vidina kasutamisel võidakse jagada andmeid saitidega %(widgetDomain)s.", + "Widgets do not use message encryption.": "Erinevalt sõnumitest vidinad ei kasuta krüptimist.", + "Widget added by": "Vidina lisaja", + "This widget may use cookies.": "See vidin võib kasutada küpsiseid.", + "Delete Widget": "Kustuta vidin", + "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Vidina kustutamisel eemaldatakse ta kõikide selle jututoa kasutajate jaoks. Kas sa kindlasti soovid seda vidinat eemaldada?", + "Delete widget": "Kustuta vidin", + "Minimize apps": "Vähenda rakendused", + "Maximize apps": "Suurenda rakendused", + "Popout widget": "Ava rakendus eraldi aknas", + "More options": "Täiendavad seadistused", + "Language Dropdown": "Keelevalik", + "Manage Integrations": "Halda lõiminguid", + "%(nameList)s %(transitionList)s": "%(nameList)s %(transitionList)s", + "%(severalUsers)sjoined %(count)s times|other": "%(severalUsers)s liitusid %(count)s korda", + "%(severalUsers)sjoined %(count)s times|one": "%(severalUsers)s liitusid", + "%(oneUser)sjoined %(count)s times|other": "%(oneUser)s liitus %(count)s korda", + "%(oneUser)sjoined %(count)s times|one": "%(oneUser)s liitus", + "%(severalUsers)sleft %(count)s times|other": "%(severalUsers)s lahkusid %(count)s korda", + "%(severalUsers)sleft %(count)s times|one": "%(severalUsers)s lahkusid", + "%(oneUser)sleft %(count)s times|other": "%(oneUser)s lahkus %(count)s korda", + "%(oneUser)sleft %(count)s times|one": "%(oneUser)s lahkus", + "%(severalUsers)sjoined and left %(count)s times|other": "%(severalUsers)s liitusid ja lahkusid %(count)s korda", + "%(severalUsers)sjoined and left %(count)s times|one": "%(severalUsers)s liitusid ja lahkusid", + "%(oneUser)sjoined and left %(count)s times|other": "%(oneUser)s liitus ja lahkus %(count)s korda", + "%(oneUser)sjoined and left %(count)s times|one": "%(oneUser)s liitus ja lahkus", + "%(severalUsers)sleft and rejoined %(count)s times|other": "%(severalUsers)s lahkusid ja liitusid uuesti %(count)s korda", + "%(severalUsers)sleft and rejoined %(count)s times|one": "%(severalUsers)s lahkusid ja liitusid uuesti", + "%(oneUser)sleft and rejoined %(count)s times|other": "%(oneUser)s lahkus ja liitus uuesti %(count)s korda", + "%(oneUser)sleft and rejoined %(count)s times|one": "%(oneUser)s lahkus ja liitus uuesti" } From 321d8ef2a19d804749f525e0900a042ac95c3534 Mon Sep 17 00:00:00 2001 From: progserega Date: Sun, 28 Jun 2020 11:06:40 +0000 Subject: [PATCH 176/294] Translated using Weblate (Russian) Currently translated at 85.2% (1996 of 2344 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index cd5a320062..e48c319595 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -2146,5 +2146,13 @@ "Messages in this room are end-to-end encrypted. Learn more & verify this user in their user profile.": "Сообщения в этой комнате зашифрованы сквозным шифрованием. Посмотрите подробности и подтвердите пользователя в его профиле.", "Send a Direct Message": "Отправить сообщение", "Light": "Светлая", - "Dark": "Темная" + "Dark": "Темная", + "Recent Conversations": "Недавние Диалоги", + "Suggestions": "Предложения", + "a key signature": "отпечаток ключа", + "Upload completed": "Отправка успешно завершена", + "Cancelled signature upload": "Отправка отпечатка отменена", + "Unable to upload": "Невозможно отправить", + "Signature upload success": "Отпечаток успешно отправлен", + "Signature upload failed": "Сбой отправки отпечатка" } From 57df5e8f6b6359b4d66b0ae9c1ee099fd95d9b90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=B2=D0=B0=D1=81=D1=8F=D0=BD228?= Date: Sun, 28 Jun 2020 11:21:06 +0000 Subject: [PATCH 177/294] Translated using Weblate (Russian) Currently translated at 85.2% (1996 of 2344 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index e48c319595..41ee56d5e7 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -2154,5 +2154,7 @@ "Cancelled signature upload": "Отправка отпечатка отменена", "Unable to upload": "Невозможно отправить", "Signature upload success": "Отпечаток успешно отправлен", - "Signature upload failed": "Сбой отправки отпечатка" + "Signature upload failed": "Сбой отправки отпечатка", + "Room name or address": "Имя или адрес комнаты", + "Unrecognised room address:": "Не удалось найти адрес комнаты:" } From d477a491607dc80c3ea617a914a8d67bd4b5cf7f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 29 Jun 2020 11:34:58 +0100 Subject: [PATCH 178/294] ToastStore fix type definition Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/stores/ToastStore.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stores/ToastStore.ts b/src/stores/ToastStore.ts index 55c48c3937..7063ba541a 100644 --- a/src/stores/ToastStore.ts +++ b/src/stores/ToastStore.ts @@ -24,7 +24,7 @@ export interface IToast; + props?: Omit, "toastKey">; // toastKey is injected by ToastContainer } /** From 51b813e2500eab2257daea1af7e40834a82742ef Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 29 Jun 2020 11:35:14 +0100 Subject: [PATCH 179/294] add timing/interval/expiry hooks Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/hooks/useTimeout.ts | 67 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/hooks/useTimeout.ts diff --git a/src/hooks/useTimeout.ts b/src/hooks/useTimeout.ts new file mode 100644 index 0000000000..911b7bc75d --- /dev/null +++ b/src/hooks/useTimeout.ts @@ -0,0 +1,67 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import {useEffect, useRef, useState} from "react"; + +type Handler = () => void; + +// Hook to simplify timeouts in functional components +export const useTimeout = (handler: Handler, timeoutMs: number) => { + // Create a ref that stores handler + const savedHandler = useRef(); + + // Update ref.current value if handler changes. + useEffect(() => { + savedHandler.current = handler; + }, [handler]); + + // Set up timer + useEffect(() => { + const timeoutID = setTimeout(() => { + savedHandler.current(); + }, timeoutMs); + return () => clearTimeout(timeoutID); + }, [timeoutMs]); +}; + +// Hook to simplify intervals in functional components +export const useInterval = (handler: Handler, intervalMs: number) => { + // Create a ref that stores handler + const savedHandler = useRef(); + + // Update ref.current value if handler changes. + useEffect(() => { + savedHandler.current = handler; + }, [handler]); + + // Set up timer + useEffect(() => { + const intervalID = setInterval(() => { + savedHandler.current(); + }, intervalMs); + return () => clearInterval(intervalID); + }, [intervalMs]); +}; + +// Hook to simplify a variable counting down to 0, handler called when it reached 0 +export const useExpiringCounter = (handler: Handler, intervalMs: number, initialCount: number) => { + const [count, setCount] = useState(initialCount); + useInterval(() => setCount(c => c - 1), intervalMs); + if (count === 0) { + handler(); + } + return count; +}; From 1a1b7e5e702f9a1222bcf576b5bdbb28710d6b51 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 29 Jun 2020 11:38:50 +0100 Subject: [PATCH 180/294] Add Generic Expiring Toast Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../views/toasts/GenericExpiringToast.tsx | 53 +++++++++++++++++++ src/components/views/toasts/GenericToast.tsx | 2 +- 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 src/components/views/toasts/GenericExpiringToast.tsx diff --git a/src/components/views/toasts/GenericExpiringToast.tsx b/src/components/views/toasts/GenericExpiringToast.tsx new file mode 100644 index 0000000000..83f43208c4 --- /dev/null +++ b/src/components/views/toasts/GenericExpiringToast.tsx @@ -0,0 +1,53 @@ +/* +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. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from "react"; + +import ToastStore from "../../../stores/ToastStore"; +import GenericToast, { IProps as IGenericToastProps } from "./GenericToast"; +import {useExpiringCounter} from "../../../hooks/useTimeout"; + +interface IProps extends IGenericToastProps { + toastKey: string; + numSeconds: number; + dismissLabel: string; + onDismiss?(); +} + +const SECOND = 1000; + +const GenericExpiringToast: React.FC = ({description, acceptLabel, dismissLabel, onAccept, onDismiss, toastKey, numSeconds}) => { + const onReject = () => { + if (onDismiss) onDismiss(); + ToastStore.sharedInstance().dismissToast(toastKey); + }; + const counter = useExpiringCounter(onReject, SECOND, numSeconds); + + let rejectLabel = dismissLabel; + if (counter > 0) { + rejectLabel += ` (${counter})`; + } + + return ; +}; + +export default GenericExpiringToast; diff --git a/src/components/views/toasts/GenericToast.tsx b/src/components/views/toasts/GenericToast.tsx index ea12641948..9f8885ba47 100644 --- a/src/components/views/toasts/GenericToast.tsx +++ b/src/components/views/toasts/GenericToast.tsx @@ -19,7 +19,7 @@ import React, {ReactChild} from "react"; import FormButton from "../elements/FormButton"; import {XOR} from "../../../@types/common"; -interface IProps { +export interface IProps { description: ReactChild; acceptLabel: string; From 02274ed6a615258b0a3ac0c28d720933e87a276b Mon Sep 17 00:00:00 2001 From: Michael Albert Date: Mon, 29 Jun 2020 07:13:40 +0000 Subject: [PATCH 181/294] Translated using Weblate (German) Currently translated at 99.9% (2342 of 2344 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 58 ++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 20ed28be1d..aacc287a5f 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -2476,5 +2476,61 @@ "System font name": "System-Schriftart", "Customise your appearance": "Verändere das Erscheinungsbild", "Appearance Settings only affect this Riot session.": "Einstellungen zum Erscheinungsbild wirken sich nur auf diese Riot Sitzung aus.", - "The authenticity of this encrypted message can't be guaranteed on this device.": "Die Echtheit dieser verschlüsselten Nachricht kann auf diesem Gerät nicht garantiert werden." + "The authenticity of this encrypted message can't be guaranteed on this device.": "Die Echtheit dieser verschlüsselten Nachricht kann auf diesem Gerät nicht garantiert werden.", + "You joined the call": "Du bist dem Anruf beigetreten", + "%(senderName)s joined the call": "%(senderName)s ist dem Anruf beigetreten", + "Call in progress": "Laufendes Gespräch", + "You left the call": "Du hast den Anruf verlassen", + "%(senderName)s left the call": "%(senderName)s hat den Anruf verlassen", + "Call ended": "Anruf beendet", + "You started a call": "Du hast einen Anruf gestartet", + "%(senderName)s started a call": "%(senderName)s hat einen Anruf gestartet", + "Waiting for answer": "Warte auf Antwort", + "%(senderName)s is calling": "%(senderName)s ruft an", + "You created the room": "Du hast den Raum erstellt", + "%(senderName)s created the room": "%(senderName)s hat den Raum erstellt", + "You made the chat encrypted": "Du hast den Raum verschlüsselt", + "%(senderName)s made the chat encrypted": "%(senderName)s hat den Raum verschlüsselt", + "You made history visible to new members": "Du hast die bisherige Kommunikation für neue Teilnehmern sichtbar gemacht", + "%(senderName)s made history visible to new members": "%(senderName)s hat die bisherige Kommunikation für neue Teilnehmern sichtbar gemacht", + "You made history visible to anyone": "Du hast die bisherige Kommunikation für alle sichtbar gemacht", + "%(senderName)s made history visible to anyone": "%(senderName)s hat die bisherige Kommunikation für alle sichtbar gemacht", + "You made history visible to future members": "Du hast die bisherige Kommunikation für zukünftige Teilnehmer sichtbar gemacht", + "%(senderName)s made history visible to future members": "%(senderName)s hat die bisherige Kommunikation für zukünftige Teilnehmer sichtbar gemacht", + "You were invited": "Du wurdest eingeladen", + "%(targetName)s was invited": "%(targetName)s wurde eingeladen", + "You left": "Du hast den Raum verlassen", + "%(targetName)s left": "%(targetName)s hat den Raum verlassen", + "You were kicked (%(reason)s)": "Du wurdest herausgeworfen (%(reason)s)", + "%(targetName)s was kicked (%(reason)s)": "%(targetName)s wurde herausgeworfen (%(reason)s)", + "You were kicked": "Du wurdest herausgeworfen", + "%(targetName)s was kicked": "%(targetName)s wurde herausgeworfen", + "You rejected the invite": "Du hast die Einladung abgelehnt", + "%(targetName)s rejected the invite": "%(targetName)s hat die Einladung abgelehnt", + "You were uninvited": "Deine Einladung wurde zurückgezogen", + "%(targetName)s was uninvited": "Die Einladung für %(targetName)s wurde zurückgezogen", + "You were banned (%(reason)s)": "Du wurdest verbannt (%(reason)s)", + "%(targetName)s was banned (%(reason)s)": "%(targetName)s wurde verbannt (%(reason)s)", + "You were banned": "Du wurdest verbannt", + "%(targetName)s was banned": "%(targetName)s wurde verbannt", + "You joined": "Du bist beigetreten", + "%(targetName)s joined": "%(targetName)s ist beigetreten", + "You changed your name": "Du hast deinen Namen geändert", + "%(targetName)s changed their name": "%(targetName)s hat den Namen geändert", + "You changed your avatar": "Du hast deinen Avatar geändert", + "%(targetName)s changed their avatar": "%(targetName)s hat den Avatar geändert", + "%(senderName)s %(emote)s": "%(senderName)s %(emote)s", + "%(senderName)s: %(message)s": "%(senderName)s: %(message)s", + "You changed the room name": "Du hast den Raumnamen geändert", + "%(senderName)s changed the room name": "%(senderName)s hat den Raumnamen geändert", + "%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s", + "%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s", + "You uninvited %(targetName)s": "Du hast die Einladung für %(targetName)s zurückgezogen", + "%(senderName)s uninvited %(targetName)s": "%(senderName)s hat die Einladung für %(targetName)s zurückgezogen", + "You invited %(targetName)s": "Du hast %(targetName)s eingeladen", + "%(senderName)s invited %(targetName)s": "%(senderName)s hat %(targetName)s eingeladen", + "You changed the room topic": "Du hast das Raumthema geändert", + "%(senderName)s changed the room topic": "%(senderName)s hat das Raumthema geändert", + "New spinner design": "Neue Warteanimation", + "Use a more compact ‘Modern’ layout": "Verwende ein kompakteres 'modernes' Layout" } From 88c195e4431a29e788991cd43d5f6fe122fe772e Mon Sep 17 00:00:00 2001 From: random Date: Mon, 29 Jun 2020 09:19:54 +0000 Subject: [PATCH 182/294] Translated using Weblate (Italian) Currently translated at 100.0% (2344 of 2344 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/it/ --- src/i18n/strings/it.json | 59 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index 1e7f6d0957..e46a66b4f4 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -2531,5 +2531,62 @@ "Modern": "Moderno", "Use a system font": "Usa un carattere di sistema", "System font name": "Nome carattere di sistema", - "The authenticity of this encrypted message can't be guaranteed on this device.": "L'autenticità di questo messaggio cifrato non può essere garantita su questo dispositivo." + "The authenticity of this encrypted message can't be guaranteed on this device.": "L'autenticità di questo messaggio cifrato non può essere garantita su questo dispositivo.", + "You joined the call": "Ti sei unito alla chiamata", + "%(senderName)s joined the call": "%(senderName)s si è unito alla chiamata", + "Call in progress": "Chiamata in corso", + "You left the call": "Hai abbandonato la chiamata", + "%(senderName)s left the call": "%(senderName)s ha abbandonato la chiamata", + "Call ended": "Chiamata terminata", + "You started a call": "Hai iniziato una chiamata", + "%(senderName)s started a call": "%(senderName)s ha iniziato una chiamata", + "Waiting for answer": "In attesa di risposta", + "%(senderName)s is calling": "%(senderName)s sta chiamando", + "You created the room": "Hai creato la stanza", + "%(senderName)s created the room": "%(senderName)s ha creato la stanza", + "You made the chat encrypted": "Hai reso la chat crittografata", + "%(senderName)s made the chat encrypted": "%(senderName)s ha reso la chat crittografata", + "You made history visible to new members": "Hai reso visibile la cronologia ai nuovi membri", + "%(senderName)s made history visible to new members": "%(senderName)s ha reso visibile la cronologia ai nuovi membri", + "You made history visible to anyone": "Hai reso visibile la cronologia a chiunque", + "%(senderName)s made history visible to anyone": "%(senderName)s ha reso visibile la cronologia a chiunque", + "You made history visible to future members": "Hai reso visibile la cronologia ai membri futuri", + "%(senderName)s made history visible to future members": "%(senderName)s ha reso visibile la cronologia ai membri futuri", + "You were invited": "Sei stato invitato", + "%(targetName)s was invited": "%(targetName)s è stato invitato", + "You left": "Sei uscito", + "%(targetName)s left": "%(targetName)s è uscito", + "You were kicked (%(reason)s)": "Sei stato buttato fuori (%(reason)s)", + "%(targetName)s was kicked (%(reason)s)": "%(targetName)s è stato buttato fuori (%(reason)s)", + "You were kicked": "Sei stato buttato fuori", + "%(targetName)s was kicked": "%(targetName)s è stato buttato fuori", + "You rejected the invite": "Hai rifiutato l'invito", + "%(targetName)s rejected the invite": "%(targetName)s ha rifiutato l'invito", + "You were uninvited": "Ti è stato revocato l'invito", + "%(targetName)s was uninvited": "È stato revocato l'invito a %(targetName)s", + "You were banned (%(reason)s)": "Sei stato bandito (%(reason)s)", + "%(targetName)s was banned (%(reason)s)": "%(targetName)s è stato bandito (%(reason)s)", + "You were banned": "Sei stato bandito", + "%(targetName)s was banned": "%(targetName)s è stato bandito", + "You joined": "Ti sei unito", + "%(targetName)s joined": "%(targetName)s si è unito", + "You changed your name": "Hai cambiato il tuo nome", + "%(targetName)s changed their name": "%(targetName)s ha cambiato il suo nome", + "You changed your avatar": "Hai cambiato il tuo avatar", + "%(targetName)s changed their avatar": "%(targetName)s ha cambiato il suo avatar", + "%(senderName)s %(emote)s": "%(senderName)s %(emote)s", + "%(senderName)s: %(message)s": "%(senderName)s: %(message)s", + "You changed the room name": "Hai cambiato il nome della stanza", + "%(senderName)s changed the room name": "%(senderName)s ha cambiato il nome della stanza", + "%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s", + "%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s", + "You uninvited %(targetName)s": "Hai revocato l'invito a %(targetName)s", + "%(senderName)s uninvited %(targetName)s": "%(senderName)s ha revocato l'invito a %(targetName)s", + "You invited %(targetName)s": "Hai invitato %(targetName)s", + "%(senderName)s invited %(targetName)s": "%(senderName)s ha invitato %(targetName)s", + "You changed the room topic": "Hai cambiato l'argomento della stanza", + "%(senderName)s changed the room topic": "%(senderName)s ha cambiato l'argomento della stanza", + "New spinner design": "Nuovo design dello spinner", + "Use a more compact ‘Modern’ layout": "Usa un layout più compatto e moderno", + "Always show first": "Mostra sempre per prime" } From 518db90b6981bb6775fb211461552140f7c93ce3 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 29 Jun 2020 13:55:06 +0100 Subject: [PATCH 183/294] Support accounts with cross signing but no SSSS Port https://github.com/matrix-org/matrix-react-sdk/pull/4717 to release --- src/components/structures/MatrixChat.tsx | 53 ++++++++----------- .../structures/auth/Registration.js | 7 +-- .../structures/auth/SetupEncryptionBody.js | 27 ++++++++-- src/i18n/strings/en_EN.json | 3 +- src/stores/SetupEncryptionStore.js | 30 +++++++++-- 5 files changed, 79 insertions(+), 41 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 634e13b103..9cb6ed078c 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1870,42 +1870,35 @@ export default class MatrixChat extends React.PureComponent { this.accountPasswordTimer = null; }, 60 * 5 * 1000); - // Wait for the client to be logged in (but not started) - // which is enough to ask the server about account data. - const loggedIn = new Promise(resolve => { - const actionHandlerRef = dis.register(payload => { - if (payload.action !== "on_logged_in") { - return; - } - dis.unregister(actionHandlerRef); - resolve(); - }); - }); - - // Create and start the client in the background - const setLoggedInPromise = Lifecycle.setLoggedIn(credentials); - await loggedIn; + // Create and start the client + await Lifecycle.setLoggedIn(credentials); const cli = MatrixClientPeg.get(); - // We're checking `isCryptoAvailable` here instead of `isCryptoEnabled` - // because the client hasn't been started yet. - const cryptoAvailable = isCryptoAvailable(); - if (!cryptoAvailable) { + const cryptoEnabled = cli.isCryptoEnabled(); + if (!cryptoEnabled) { this.onLoggedIn(); } - this.setState({ pendingInitialSync: true }); - await this.firstSyncPromise.promise; - - if (!cryptoAvailable) { - this.setState({ pendingInitialSync: false }); - return setLoggedInPromise; + const promisesList = [this.firstSyncPromise.promise]; + if (cryptoEnabled) { + // wait for the client to finish downloading cross-signing keys for us so we + // know whether or not we have keys set up on this account + promisesList.push(cli.downloadKeys([cli.getUserId()])); } - // Test for the master cross-signing key in SSSS as a quick proxy for - // whether cross-signing has been set up on the account. - const masterKeyInStorage = !!cli.getAccountData("m.cross_signing.master"); - if (masterKeyInStorage) { + // Now update the state to say we're waiting for the first sync to complete rather + // than for the login to finish. + this.setState({ pendingInitialSync: true }); + + await Promise.all(promisesList); + + if (!cryptoEnabled) { + this.setState({ pendingInitialSync: false }); + return; + } + + const crossSigningIsSetUp = cli.getStoredCrossSigningForUser(cli.getUserId()); + if (crossSigningIsSetUp) { this.setStateForNewView({ view: Views.COMPLETE_SECURITY }); } else if (await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing")) { this.setStateForNewView({ view: Views.E2E_SETUP }); @@ -1913,8 +1906,6 @@ export default class MatrixChat extends React.PureComponent { this.onLoggedIn(); } this.setState({ pendingInitialSync: false }); - - return setLoggedInPromise; }; // complete security / e2e setup has finished diff --git a/src/components/structures/auth/Registration.js b/src/components/structures/auth/Registration.js index 6349614d72..3b5f5676dc 100644 --- a/src/components/structures/auth/Registration.js +++ b/src/components/structures/auth/Registration.js @@ -378,7 +378,7 @@ export default createReactClass({ } if (response.access_token) { - const cli = await this.props.onLoggedIn({ + await this.props.onLoggedIn({ userId: response.user_id, deviceId: response.device_id, homeserverUrl: this.state.matrixClient.getHomeserverUrl(), @@ -386,7 +386,7 @@ export default createReactClass({ accessToken: response.access_token, }, this.state.formVals.password); - this._setupPushers(cli); + this._setupPushers(); // we're still busy until we get unmounted: don't show the registration form again newState.busy = true; } else { @@ -397,10 +397,11 @@ export default createReactClass({ this.setState(newState); }, - _setupPushers: function(matrixClient) { + _setupPushers: function() { if (!this.props.brand) { return Promise.resolve(); } + const matrixClient = MatrixClientPeg.get(); return matrixClient.getPushers().then((resp)=>{ const pushers = resp.pushers; for (let i = 0; i < pushers.length; ++i) { diff --git a/src/components/structures/auth/SetupEncryptionBody.js b/src/components/structures/auth/SetupEncryptionBody.js index 26534c6e02..f2e702c8cb 100644 --- a/src/components/structures/auth/SetupEncryptionBody.js +++ b/src/components/structures/auth/SetupEncryptionBody.js @@ -28,6 +28,14 @@ import { PHASE_FINISHED, } from '../../../stores/SetupEncryptionStore'; +function keyHasPassphrase(keyInfo) { + return ( + keyInfo.passphrase && + keyInfo.passphrase.salt && + keyInfo.passphrase.iterations + ); +} + export default class SetupEncryptionBody extends React.Component { static propTypes = { onFinished: PropTypes.func.isRequired, @@ -108,6 +116,21 @@ export default class SetupEncryptionBody extends React.Component { member={MatrixClientPeg.get().getUser(this.state.verificationRequest.otherUserId)} />; } else if (phase === PHASE_INTRO) { + const store = SetupEncryptionStore.sharedInstance(); + let recoveryKeyPrompt; + if (store.keyInfo && keyHasPassphrase(store.keyInfo)) { + recoveryKeyPrompt = _t("Use Recovery Key or Passphrase"); + } else if (store.keyInfo) { + recoveryKeyPrompt = _t("Use Recovery Key"); + } + + let useRecoveryKeyButton; + if (recoveryKeyPrompt) { + useRecoveryKeyButton = + {recoveryKeyPrompt} + ; + } + return (

          {_t( @@ -131,9 +154,7 @@ export default class SetupEncryptionBody extends React.Component {

          - - {_t("Use Recovery Passphrase or Key")} - + {useRecoveryKeyButton} {_t("Skip")} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9a41517664..e927f0202b 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2121,10 +2121,11 @@ "You can now close this window or log in to your new account.": "You can now close this window or log in to your new account.", "Registration Successful": "Registration Successful", "Create your account": "Create your account", + "Use Recovery Key or Passphrase": "Use Recovery Key or Passphrase", + "Use Recovery Key": "Use Recovery Key", "Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.": "Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.", "This requires the latest Riot on your other devices:": "This requires the latest Riot on your other devices:", "or another cross-signing capable Matrix client": "or another cross-signing capable Matrix client", - "Use Recovery Passphrase or Key": "Use Recovery Passphrase or Key", "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.", "Your new session is now verified. Other users will see it as trusted.": "Your new session is now verified. Other users will see it as trusted.", "Without completing security on this session, it won’t have access to encrypted messages.": "Without completing security on this session, it won’t have access to encrypted messages.", diff --git a/src/stores/SetupEncryptionStore.js b/src/stores/SetupEncryptionStore.js index ae1f998b02..b55b5c223c 100644 --- a/src/stores/SetupEncryptionStore.js +++ b/src/stores/SetupEncryptionStore.js @@ -36,11 +36,20 @@ export class SetupEncryptionStore extends EventEmitter { return; } this._started = true; - this.phase = PHASE_INTRO; + this.phase = PHASE_BUSY; this.verificationRequest = null; this.backupInfo = null; - MatrixClientPeg.get().on("crypto.verification.request", this.onVerificationRequest); - MatrixClientPeg.get().on('userTrustStatusChanged', this._onUserTrustStatusChanged); + + // ID of the key that the secrets we want are encrypted with + this.keyId = null; + // Descriptor of the key that the secrets we want are encrypted with + this.keyInfo = null; + + const cli = MatrixClientPeg.get(); + cli.on("crypto.verification.request", this.onVerificationRequest); + cli.on('userTrustStatusChanged', this._onUserTrustStatusChanged); + + this.fetchKeyInfo(); } stop() { @@ -57,6 +66,21 @@ export class SetupEncryptionStore extends EventEmitter { } } + async fetchKeyInfo() { + const keys = await MatrixClientPeg.get().isSecretStored('m.cross_signing.master', false); + if (keys === null || Object.keys(keys).length === 0) { + this.keyId = null; + this.keyInfo = null; + } else { + // If the secret is stored under more than one key, we just pick an arbitrary one + this.keyId = Object.keys(keys)[0]; + this.keyInfo = keys[this.keyId]; + } + + this.phase = PHASE_INTRO; + this.emit("update"); + } + async usePassPhrase() { this.phase = PHASE_BUSY; this.emit("update"); From b2b909aa53b545c835de591e48ebef593cf8b219 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 29 Jun 2020 15:40:20 +0100 Subject: [PATCH 184/294] Including start_sso and start_cas in redirect loop prevention Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/MatrixChat.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 79bdf743ce..02f08211b9 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1932,11 +1932,12 @@ export default class MatrixChat extends React.PureComponent { getFragmentAfterLogin() { let fragmentAfterLogin = ""; - if (this.props.initialScreenAfterLogin && + const initialScreenAfterLogin = this.props.initialScreenAfterLogin; + if (initialScreenAfterLogin && // XXX: workaround for https://github.com/vector-im/riot-web/issues/11643 causing a login-loop - !["welcome", "login", "register"].includes(this.props.initialScreenAfterLogin.screen) + !["welcome", "login", "register", "start_sso", "start_cas"].includes(initialScreenAfterLogin.screen) ) { - fragmentAfterLogin = `/${this.props.initialScreenAfterLogin.screen}`; + fragmentAfterLogin = `/${initialScreenAfterLogin.screen}`; } return fragmentAfterLogin; } From 9329be3bb894ecae689c56d2662795dc34ee6ade Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 29 Jun 2020 15:59:36 +0100 Subject: [PATCH 185/294] Prepare changelog for v2.8.1 --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21bfbf0a93..aa476ae6a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +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) + + * Support accounts with cross signing but no SSSS + [\#4852](https://github.com/matrix-org/matrix-react-sdk/pull/4852) + Changes in [2.8.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.8.0) (2020-06-23) =================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.8.0-rc.1...v2.8.0) From e43311c0f468c56e2a361c6f1c633fce348b5776 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 29 Jun 2020 15:59:37 +0100 Subject: [PATCH 186/294] v2.8.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 591922498f..c623c64e01 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "2.8.0", + "version": "2.8.1", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From 9f6893ef2b4ab2e69ebcf3c302f21e7504c26bf5 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 29 Jun 2020 16:27:59 +0100 Subject: [PATCH 187/294] Fix /join slash command via servers including room id as a via Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/SlashCommands.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 7ebdc4ee3b..f667c47b3c 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -495,8 +495,7 @@ export const Commands = [ }); return success(); } else if (params[0][0] === '!') { - const roomId = params[0]; - const viaServers = params.splice(0); + const [roomId, ...viaServers] = params; dis.dispatch({ action: 'view_room', From adf186f568037b18e0ae9302d639c1a39dbe0224 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 29 Jun 2020 23:02:10 +0100 Subject: [PATCH 188/294] Fix RoomTile2 Context Menu to match Figma Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/views/rooms/_RoomTile2.scss | 8 ++++---- src/components/views/rooms/RoomTile2.tsx | 24 +++++++++++------------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/res/css/views/rooms/_RoomTile2.scss b/res/css/views/rooms/_RoomTile2.scss index 2845068de3..f1516ee0e3 100644 --- a/res/css/views/rooms/_RoomTile2.scss +++ b/res/css/views/rooms/_RoomTile2.scss @@ -118,8 +118,7 @@ limitations under the License. } .mx_RoomTile2_menuButton::before { - top: 8px; - left: -1px; // this is off-center to align it with the badges + left: 1px; // this is off-center to align it with the badges mask-image: url('$(res)/img/feather-customised/more-horizontal.svg'); } @@ -133,9 +132,10 @@ limitations under the License. } .mx_RoomTile2_menuButton { - width: 18px; - height: 32px; + width: 16px; + height: 16px; visibility: visible; + margin: auto 0; } } } diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index 1284728855..59741635f1 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -64,8 +64,14 @@ interface IState { generalMenuDisplayed: boolean; } +export const contextMenuBelow = (elementRect) => { + const left = elementRect.left + window.pageXOffset - 6; + let top = elementRect.bottom + window.pageYOffset + 21; + const chevronFace = "none"; + return {left, top, chevronFace}; +}; + export default class RoomTile2 extends React.Component { - private roomTileRef: React.RefObject = createRef(); private generalMenuButtonRef: React.RefObject = createRef(); // TODO: a11y: https://github.com/vector-im/riot-web/issues/14180 @@ -159,18 +165,10 @@ export default class RoomTile2 extends React.Component { let contextMenu = null; if (this.state.generalMenuDisplayed) { // The context menu appears within the list, so use the room tile as a reference point - const elementRect = this.roomTileRef.current.getBoundingClientRect(); + const elementRect = this.generalMenuButtonRef.current.getBoundingClientRect(); contextMenu = ( - -
          + +
          • @@ -280,7 +278,7 @@ export default class RoomTile2 extends React.Component { const avatarSize = 32; return ( - + {({onFocus, isActive, ref}) => Date: Tue, 30 Jun 2020 00:16:51 +0100 Subject: [PATCH 189/294] Add room notifications context menu and non-default indicator to RoomTile2 Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/views/rooms/_RoomTile2.scss | 37 ++++++---- src/components/views/rooms/RoomTile2.tsx | 87 +++++++++++++++++++++++- src/i18n/strings/en_EN.json | 4 +- 3 files changed, 111 insertions(+), 17 deletions(-) diff --git a/res/css/views/rooms/_RoomTile2.scss b/res/css/views/rooms/_RoomTile2.scss index f1516ee0e3..78a7732882 100644 --- a/res/css/views/rooms/_RoomTile2.scss +++ b/res/css/views/rooms/_RoomTile2.scss @@ -92,20 +92,17 @@ limitations under the License. justify-content: center; } - // The menu button is hidden by default - // TODO: [Notifications] Use mx_RoomTile2_notificationsButton, similar to the following approach: - // https://github.com/matrix-org/matrix-react-sdk/blob/2180a56074f3698fc0241c309a72ba6cad802d1c/res/css/views/rooms/_RoomSublist2.scss#L48-L76 - // You'll need to do the same down below on the &:hover selector for the tile. - // See https://github.com/vector-im/riot-web/issues/13961. - // ... also remove this 5 line TODO comment. .mx_RoomTile2_menuButton, .mx_RoomTile2_notificationsButton { - width: 0; - height: 0; - visibility: hidden; + width: 20px; + height: 20px; + margin: auto 0 auto 8px; position: relative; + display: none; &::before { + top: 2px; + left: 2px; content: ''; width: 16px; height: 16px; @@ -117,8 +114,11 @@ limitations under the License. } } + .mx_RoomTile2_notificationsButton.mx_RoomTile2_notificationsButton_show { + display: block; + } + .mx_RoomTile2_menuButton::before { - left: 1px; // this is off-center to align it with the badges mask-image: url('$(res)/img/feather-customised/more-horizontal.svg'); } @@ -131,11 +131,9 @@ limitations under the License. visibility: hidden; } + .mx_RoomTile2_notificationsButton, .mx_RoomTile2_menuButton { - width: 16px; - height: 16px; - visibility: visible; - margin: auto 0; + display: block; } } } @@ -158,6 +156,17 @@ limitations under the License. } } +// We use these both in context menus and the room tiles +.mx_RoomTile2_iconBell::before { + mask-image: url('$(res)/img/feather-customised/bell.svg'); +} +.mx_RoomTile2_iconBellDot::before { + mask-image: url('$(res)/img/feather-customised/bell-notification.custom.svg'); +} +.mx_RoomTile2_iconBellCrossed::before { + mask-image: url('$(res)/img/feather-customised/bell-crossed.svg'); +} + .mx_RoomTile2_contextMenu { .mx_RoomTile2_contextMenu_redRow { .mx_AccessibleButton { diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index 59741635f1..52b3d444ac 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -36,6 +36,7 @@ import { ContextMenu, ContextMenuButton } from "../../structures/ContextMenu"; import { DefaultTagID, TagID } from "../../../stores/room-list/models"; import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore"; import RoomTileIcon from "./RoomTileIcon"; +import { getRoomNotifsState, ALL_MESSAGES, ALL_MESSAGES_LOUD, MENTIONS_ONLY, MUTE } from "../../../RoomNotifs"; // 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 @@ -61,6 +62,7 @@ interface IState { hover: boolean; notificationState: INotificationState; selected: boolean; + notificationsMenuDisplayed: boolean; generalMenuDisplayed: boolean; } @@ -72,6 +74,7 @@ export const contextMenuBelow = (elementRect) => { }; export default class RoomTile2 extends React.Component { + private notificationsMenuButtonRef: React.RefObject = createRef(); private generalMenuButtonRef: React.RefObject = createRef(); // TODO: a11y: https://github.com/vector-im/riot-web/issues/14180 @@ -83,6 +86,7 @@ export default class RoomTile2 extends React.Component { hover: false, notificationState: new TagSpecificNotificationState(this.props.room, this.props.tag), selected: ActiveRoomObserver.activeRoomId === this.props.room.roomId, + notificationsMenuDisplayed: false, generalMenuDisplayed: false, }; @@ -117,6 +121,18 @@ export default class RoomTile2 extends React.Component { this.setState({selected: isActive}); }; + private onNotificationsMenuOpenClick = (ev: InputEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + this.setState({notificationsMenuDisplayed: true}); + }; + + private onCloseNotificationsMenu = (ev: InputEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + this.setState({notificationsMenuDisplayed: false}); + }; + private onGeneralMenuOpenClick = (ev: InputEvent) => { ev.preventDefault(); ev.stopPropagation(); @@ -159,12 +175,78 @@ export default class RoomTile2 extends React.Component { this.setState({generalMenuDisplayed: false}); // hide the menu }; + private renderNotificationsMenu(): React.ReactElement { + if (this.props.isMinimized) return null; // no menu when minimized + + let contextMenu = null; + if (this.state.notificationsMenuDisplayed) { + const elementRect = this.notificationsMenuButtonRef.current.getBoundingClientRect(); + contextMenu = ( + +
            +
            +
              +
            • + + + {_t("All messages")} + +
            • +
            • + + + {_t("Default")} + +
            • +
            • + + + {_t("Mentions & Keywords")} + +
            • +
            • + + + {_t("None")} + +
            • +
            +
            +
            +
            + ); + } + + const state = getRoomNotifsState(this.props.room.roomId); + const classes = classNames("mx_RoomTile2_notificationsButton", { + // Show bell icon for the default case too. + mx_RoomTile2_iconBell: state === ALL_MESSAGES_LOUD || state === ALL_MESSAGES, + mx_RoomTile2_iconBellDot: state === MENTIONS_ONLY, + mx_RoomTile2_iconBellCrossed: state === MUTE, + // XXX: RoomNotifs assumes ALL_MESSAGES is default, this is wrong, + // but cannot be fixed until FTUE Notifications lands. + mx_RoomTile2_notificationsButton_show: state !== ALL_MESSAGES, + }); + + return ( + + + {contextMenu} + + ); + } + private renderGeneralMenu(): React.ReactElement { if (this.props.isMinimized) return null; // no menu when minimized let contextMenu = null; if (this.state.generalMenuDisplayed) { - // The context menu appears within the list, so use the room tile as a reference point const elementRect = this.generalMenuButtonRef.current.getBoundingClientRect(); contextMenu = ( @@ -227,7 +309,7 @@ export default class RoomTile2 extends React.Component { const classes = classNames({ 'mx_RoomTile2': true, 'mx_RoomTile2_selected': this.state.selected, - 'mx_RoomTile2_hasMenuOpen': this.state.generalMenuDisplayed, + 'mx_RoomTile2_hasMenuOpen': this.state.generalMenuDisplayed || this.state.notificationsMenuDisplayed, 'mx_RoomTile2_minimized': this.props.isMinimized, }); @@ -298,6 +380,7 @@ export default class RoomTile2 extends React.Component {
            {badge}
            + {this.renderNotificationsMenu()} {this.renderGeneralMenu()}
            } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 495300f3fe..0ef675c7c8 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1218,6 +1218,9 @@ "%(count)s unread messages.|one": "1 unread message.", "Unread mentions.": "Unread mentions.", "Unread messages.": "Unread messages.", + "All messages": "All messages", + "Mentions & Keywords": "Mentions & Keywords", + "Notification options": "Notification options", "Favourite": "Favourite", "Low Priority": "Low Priority", "Leave Room": "Leave Room", @@ -1894,7 +1897,6 @@ "Failed to forget room %(errCode)s": "Failed to forget room %(errCode)s", "Notification settings": "Notification settings", "All messages (noisy)": "All messages (noisy)", - "All messages": "All messages", "Mentions only": "Mentions only", "Leave": "Leave", "Forget": "Forget", From 6b2ba8caed50ac0c2a9b2871bf40d05d87d31bde Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 30 Jun 2020 00:27:32 +0100 Subject: [PATCH 190/294] Add svgs Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/img/feather-customised/bell-crossed.svg | 4 ++++ res/img/feather-customised/bell-notification.custom.svg | 5 +++++ res/img/feather-customised/bell.svg | 3 +++ 3 files changed, 12 insertions(+) create mode 100644 res/img/feather-customised/bell-crossed.svg create mode 100644 res/img/feather-customised/bell-notification.custom.svg create mode 100644 res/img/feather-customised/bell.svg diff --git a/res/img/feather-customised/bell-crossed.svg b/res/img/feather-customised/bell-crossed.svg new file mode 100644 index 0000000000..3ca24662b9 --- /dev/null +++ b/res/img/feather-customised/bell-crossed.svg @@ -0,0 +1,4 @@ + + + + diff --git a/res/img/feather-customised/bell-notification.custom.svg b/res/img/feather-customised/bell-notification.custom.svg new file mode 100644 index 0000000000..7bfd551f97 --- /dev/null +++ b/res/img/feather-customised/bell-notification.custom.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/feather-customised/bell.svg b/res/img/feather-customised/bell.svg new file mode 100644 index 0000000000..b6bc5ec502 --- /dev/null +++ b/res/img/feather-customised/bell.svg @@ -0,0 +1,3 @@ + + + From 1e457994f903a05bcf6136b5d2b3a7c0a7bad977 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 30 Jun 2020 11:10:12 +0100 Subject: [PATCH 191/294] More padding between header & text in radio button --- .../views/dialogs/secretstorage/_CreateSecretStorageDialog.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss b/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss index b073bac93c..d30803b1f0 100644 --- a/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss +++ b/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss @@ -91,6 +91,7 @@ limitations under the License. color: $dialog-title-fg-color; font-weight: 600; font-size: $font-18px; + padding-bottom: 10px; } .mx_CreateSecretStorageDialog_optionIcon { From 7d7bafb1ea1e6c18526521f1bfe66f8d930d2f17 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 30 Jun 2020 16:23:52 +0100 Subject: [PATCH 192/294] De-duplicate rooms from the room autocomplete provider Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/autocomplete/RoomProvider.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/autocomplete/RoomProvider.tsx b/src/autocomplete/RoomProvider.tsx index 0d8aac4218..f14fa3bbfa 100644 --- a/src/autocomplete/RoomProvider.tsx +++ b/src/autocomplete/RoomProvider.tsx @@ -25,9 +25,9 @@ import {MatrixClientPeg} from '../MatrixClientPeg'; import QueryMatcher from './QueryMatcher'; import {PillCompletion} from './Components'; import * as sdk from '../index'; -import _sortBy from 'lodash/sortBy'; import {makeRoomPermalink} from "../utils/permalinks/Permalinks"; import {ICompletion, ISelectionRange} from "./Autocompleter"; +import { uniqBy, sortBy } from 'lodash'; const ROOM_REGEX = /\B#\S*/g; @@ -91,10 +91,11 @@ export default class RoomProvider extends AutocompleteProvider { this.matcher.setObjects(matcherObjects); const matchedString = command[0]; completions = this.matcher.match(matchedString); - completions = _sortBy(completions, [ + completions = sortBy(completions, [ (c) => score(matchedString, c.displayedAlias), (c) => c.displayedAlias.length, ]); + completions = uniqBy(completions, (match) => match.room); completions = completions.map((room) => { return { completion: room.displayedAlias, From 022cc5ea324ac7f343c411ce03e8c25c2481699e Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Tue, 30 Jun 2020 02:14:16 +0000 Subject: [PATCH 193/294] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2345 of 2345 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index 9384e8846a..8f4d9b12d3 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2592,5 +2592,6 @@ "You changed the room topic": "您變更了聊天室主題", "%(senderName)s changed the room topic": "%(senderName)s 變更了聊天室主題", "New spinner design": "新的微調器設計", - "Use a more compact ‘Modern’ layout": "使用更簡潔的「現代」佈局" + "Use a more compact ‘Modern’ layout": "使用更簡潔的「現代」佈局", + "Message deleted on %(date)s": "訊息刪除於 %(date)s" } From 2d6142d132b7fffe154a1792b43d8710de7cf425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Tue, 30 Jun 2020 07:24:42 +0000 Subject: [PATCH 194/294] Translated using Weblate (French) Currently translated at 100.0% (2345 of 2345 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/fr/ --- src/i18n/strings/fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index f781df55e2..bc0cccbe07 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -2593,5 +2593,6 @@ "%(senderName)s invited %(targetName)s": "%(senderName)s a invité %(targetName)s", "You changed the room topic": "Vous avez changé le sujet du salon", "%(senderName)s changed the room topic": "%(senderName)s a changé le sujet du salon", - "New spinner design": "Nouveau design du spinner" + "New spinner design": "Nouveau design du spinner", + "Message deleted on %(date)s": "Message supprimé le %(date)s" } From 08fa7912b3158ee77ac3364ed147876379d6fb37 Mon Sep 17 00:00:00 2001 From: XoseM Date: Tue, 30 Jun 2020 13:35:04 +0000 Subject: [PATCH 195/294] Translated using Weblate (Galician) Currently translated at 100.0% (2345 of 2345 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/gl/ --- src/i18n/strings/gl.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index b0b61e3d40..fa8e11863f 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -2424,5 +2424,6 @@ "You invited %(targetName)s": "Convidaches a %(targetName)s", "%(senderName)s invited %(targetName)s": "%(senderName)s convidou a %(targetName)s", "You changed the room topic": "Cambiaches o tema da sala", - "%(senderName)s changed the room topic": "%(senderName)s cambiou o asunto da sala" + "%(senderName)s changed the room topic": "%(senderName)s cambiou o asunto da sala", + "Message deleted on %(date)s": "Mensaxe eliminada o %(date)s" } From bf4305f00c4c09f728f7dc5277be781c10accbe9 Mon Sep 17 00:00:00 2001 From: Michael Albert Date: Tue, 30 Jun 2020 15:11:23 +0000 Subject: [PATCH 196/294] Translated using Weblate (German) Currently translated at 99.9% (2343 of 2345 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index aacc287a5f..4786845418 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -2532,5 +2532,6 @@ "You changed the room topic": "Du hast das Raumthema geändert", "%(senderName)s changed the room topic": "%(senderName)s hat das Raumthema geändert", "New spinner design": "Neue Warteanimation", - "Use a more compact ‘Modern’ layout": "Verwende ein kompakteres 'modernes' Layout" + "Use a more compact ‘Modern’ layout": "Verwende ein kompakteres 'modernes' Layout", + "Message deleted on %(date)s": "Nachricht am %(date)s gelöscht" } From 41ac9801965b9526578342cc92753b6bf7851a15 Mon Sep 17 00:00:00 2001 From: random Date: Tue, 30 Jun 2020 10:03:36 +0000 Subject: [PATCH 197/294] Translated using Weblate (Italian) Currently translated at 100.0% (2345 of 2345 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/it/ --- src/i18n/strings/it.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index e46a66b4f4..69b879ae19 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -2588,5 +2588,6 @@ "%(senderName)s changed the room topic": "%(senderName)s ha cambiato l'argomento della stanza", "New spinner design": "Nuovo design dello spinner", "Use a more compact ‘Modern’ layout": "Usa un layout più compatto e moderno", - "Always show first": "Mostra sempre per prime" + "Always show first": "Mostra sempre per prime", + "Message deleted on %(date)s": "Messaggio eliminato il %(date)s" } From 7caf2d5459cf472a470dea7cda48bee7d21be827 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 30 Jun 2020 17:56:50 +0100 Subject: [PATCH 198/294] remove rogue blank line --- .../views/dialogs/secretstorage/AccessSecretStorageDialog.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js index e5b75f4dfc..5c01a6907f 100644 --- a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js +++ b/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js @@ -112,7 +112,6 @@ export default class AccessSecretStorageDialog extends React.PureComponent { // the did before (otherwise the onchange wouldn't fire) if (this._fileUpload.current) this._fileUpload.current.value = null; - // We don't use Field's validation here because a) we want it in a separate place rather // than in a tooltip and b) we want it to display feedback based on the uploaded file // as well as the text box. Ideally we would refactor Field's validation logic so we could From 51118863c75ed37a233497ba5665ffe4de4bd043 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 30 Jun 2020 18:40:52 +0100 Subject: [PATCH 199/294] Fix jumping to read marker for events without tiles It is possible for your read marker to be set to an event without a tile (like a reaction). We would still render the read marker at the position of those events in the timeline, even though there's no matching tile, which breaks a core assumption of jump to read marker path: it assumes that if the read marker node is present in the DOM, then there must also be an event tile with a scroll token matching the event ID. This fixes the situation by setting a scroll token on the read marker so it can always be scrolled to, no matter what kind of event it might represent. Fixes vector-im/riot-web#10975 --- src/components/structures/MessagePanel.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index d11fee6360..7567786af3 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -388,8 +388,11 @@ export default class MessagePanel extends React.Component { } return ( -
          • +
          • { hr }
          • ); From e83a941140a489fce29b80418f2639c0c14da660 Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Tue, 30 Jun 2020 17:15:56 +0000 Subject: [PATCH 200/294] Translated using Weblate (Albanian) Currently translated at 99.9% (2352 of 2355 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/sq/ --- src/i18n/strings/sq.json | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sq.json b/src/i18n/strings/sq.json index 4ea5ff25cb..ed5deada2d 100644 --- a/src/i18n/strings/sq.json +++ b/src/i18n/strings/sq.json @@ -2582,5 +2582,26 @@ "You changed the room topic": "Ndryshuat temën e dhomës", "%(senderName)s changed the room topic": "%(senderName)s ndryshoi temën e dhomës", "Use a more compact ‘Modern’ layout": "Përdorni një skemë ‘Modern’ më kompakte", - "The authenticity of this encrypted message can't be guaranteed on this device.": "Mirëfilltësia e këtij mesazhi të fshehtëzuar s’mund të garantohet në këtë pajisje." + "The authenticity of this encrypted message can't be guaranteed on this device.": "Mirëfilltësia e këtij mesazhi të fshehtëzuar s’mund të garantohet në këtë pajisje.", + "Message deleted on %(date)s": "Mesazh i fshirë më %(date)s", + "Wrong file type": "Lloj i gabuar kartele", + "Wrong Recovery Key": "Kyç Rimarrjesh i Gabuar", + "Invalid Recovery Key": "Kyç Rimarrjesh i Pavlefshëm", + "Security Phrase": "Frazë Sigurie", + "Enter your Security Phrase or to continue.": "Që të vazhdohet, jepni Frazën tuaj të Sigurisë ose .", + "Security Key": "Kyç Sigurie", + "Use your Security Key to continue.": "Që të vazhdohet përdorni Kyçin tuaj të Sigurisë.", + "Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.": "Mbrohuni kundër humbjes së hyrjes në mesazhe & të dhëna të fshehtëzuara duke kopjeruajtur kyçe fshehtëzimi në shërbyesin tuaj.", + "Generate a Security Key": "Prodhoni një Kyç Sigurie", + "We’ll generate a Security Key for you to store somewhere safe, like a password manager or a safe.": "Do të prodhojmë për ju një Kyç Sigurie që ta depozitoni diku të parrezik, bie fjala në një përgjegjës fjalëkalimesh ose në një kasafortë.", + "Enter a Security Phrase": "Jepni një Frazë Sigurie", + "Use a secret phrase only you know, and optionally save a Security Key to use for backup.": "Jepni një frazë të fshehtë që e dini vetëm ju, dhe, në daçi, ruani një Kyç Sigurie për ta përdorur për kopjeruajtje.", + "Enter a security phrase only you know, as it’s used to safeguard your data. To be secure, you shouldn’t re-use your account password.": "Jepni një frazë sigurie që e dini vetëm ju, ngaqë përdoret për të mbrojtur të dhënat tuaja. Që të jeni të sigurt, s’duhet të ripërdorni fjalëkalimin e llogarisë tuaj.", + "Store your Security Key somewhere safe, like a password manager or a safe, as it’s used to safeguard your encrypted data.": "Depozitojeni Kyçin tuaj të Sigurisë diku të parrezik, bie fjala në një përgjegjës fjalëkalimesh ose në një kasafortë, ngaqë përdoret për të mbrojtur të dhënat tuaja të fshehtëzuara.", + "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.": "Nëse e anuloni tani, mund të humbni mesazhe & të dhëna të fshehtëzuara, nëse humbni hyrjen te kredencialet tuaja të hyrjeve.", + "You can also set up Secure Backup & manage your keys in Settings.": "Mundeni edhe të ujdisni Kopjeruajtje të Sigurt & administroni kyçet tuaj që nga Rregullimet.", + "Set up Secure backup": "Ujdisni kopjeruajtje të Sigurt", + "Set a Security Phrase": "Caktoni një Frazë Sigurie", + "Confirm Security Phrase": "Ripohoni Frazë Sigurie", + "Save your Security Key": "Ruani Kyçin tuaj të Sigurisë" } From d8bc20932efd8024d016af6c789a736b904889c2 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Tue, 30 Jun 2020 18:08:39 +0000 Subject: [PATCH 201/294] Translated using Weblate (Hungarian) Currently translated at 100.0% (2355 of 2355 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/hu/ --- src/i18n/strings/hu.json | 79 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index 05d68fd890..378f71fb49 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -2525,5 +2525,82 @@ "Message layout": "Üzenet kinézete", "Compact": "Egyszerű", "Modern": "Modern", - "The authenticity of this encrypted message can't be guaranteed on this device.": "A titkosított üzenetek valódiságát ezen az eszközön nem lehet garantálni." + "The authenticity of this encrypted message can't be guaranteed on this device.": "A titkosított üzenetek valódiságát ezen az eszközön nem lehet garantálni.", + "You joined the call": "Csatlakoztál a hívásba", + "%(senderName)s joined the call": "%(senderName)s csatlakozott a híváshoz", + "Call in progress": "Hívás folyamatban van", + "You left the call": "Kiléptél a hívásból", + "%(senderName)s left the call": "%(senderName)s kilépett a hívásból", + "Call ended": "Hívás befejeződött", + "You started a call": "Hívást kezdeményeztél", + "%(senderName)s started a call": "%(senderName)s hívást kezdeményezett", + "Waiting for answer": "Válaszra várakozás", + "%(senderName)s is calling": "%(senderName)s hív", + "You created the room": "Létrehoztál egy szobát", + "%(senderName)s created the room": "%(senderName)s létrehozott egy szobát", + "You made the chat encrypted": "A beszélgetést titkosítottá tetted", + "%(senderName)s made the chat encrypted": "%(senderName)s titkosítottá tette a beszélgetést", + "You made history visible to new members": "A régi beszélgetéseket láthatóvá tetted az új tagok számára", + "%(senderName)s made history visible to new members": "%(senderName)s a régi beszélgetéseket láthatóvá tette az új tagok számára", + "You made history visible to anyone": "A régi beszélgetéseket láthatóvá tette mindenki számára", + "%(senderName)s made history visible to anyone": "%(senderName)s a régi beszélgetéseket láthatóvá tette mindenki számára", + "You made history visible to future members": "A régi beszélgetéseket láthatóvá tetted a leendő tagok számára", + "%(senderName)s made history visible to future members": "%(senderName)s a régi beszélgetéseket láthatóvá tette a leendő tagok számára", + "You were invited": "Meghívtak", + "%(targetName)s was invited": "Meghívták őt: %(targetName)s", + "You left": "Távoztál", + "%(targetName)s left": "%(targetName)s távozott", + "You were kicked (%(reason)s)": "Kirúgtak (%(reason)s)", + "%(targetName)s was kicked (%(reason)s)": "Kirúgták őt: %(targetName)s (%(reason)s)", + "You were kicked": "Kirúgtak", + "%(targetName)s was kicked": "Kirúgták őt: %(targetName)s", + "You rejected the invite": "A meghívót elutasítottad", + "%(targetName)s rejected the invite": "%(targetName)s elutasította a meghívót", + "You were uninvited": "A meghívódat visszavonták", + "%(targetName)s was uninvited": "A meghívóját visszavonták neki: %(targetName)s", + "You were banned (%(reason)s)": "Kitiltottak (%(reason)s)", + "%(targetName)s was banned (%(reason)s)": "Kitiltották őt: %(targetName)s (%(reason)s)", + "You were banned": "Kitiltottak", + "%(targetName)s was banned": "Kitiltották őt: %(targetName)s", + "You joined": "Beléptél", + "%(targetName)s joined": "%(targetName)s belépett", + "You changed your name": "A neved megváltoztattad", + "%(targetName)s changed their name": "%(targetName)s megváltoztatta a nevét", + "You changed your avatar": "A profilképedet megváltoztattad", + "%(targetName)s changed their avatar": "%(targetName)s megváltoztatta a profilképét", + "%(senderName)s %(emote)s": "%(senderName)s %(emote)s", + "%(senderName)s: %(message)s": "%(senderName)s: %(message)s", + "You changed the room name": "A szoba nevét megváltoztattad", + "%(senderName)s changed the room name": "%(senderName)s megváltoztatta a szoba nevét", + "%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s", + "%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s", + "You uninvited %(targetName)s": "A meghívóját visszavontad neki: %(targetName)s", + "%(senderName)s uninvited %(targetName)s": "%(senderName)s visszavonta a meghívóját neki: %(targetName)s", + "You invited %(targetName)s": "Meghívtad őt: %(targetName)s", + "%(senderName)s invited %(targetName)s": "%(senderName)s meghívta őt: %(targetName)s", + "You changed the room topic": "A szoba témáját megváltoztattad", + "%(senderName)s changed the room topic": "%(senderName)s megváltoztatta a szoba témáját", + "New spinner design": "Új várakozási animáció", + "Use a more compact ‘Modern’ layout": "Egyszerűbb 'Modern' kinézet használata", + "Message deleted on %(date)s": "Az üzenetet ekkor törölték: %(date)s", + "Wrong file type": "A fájl típus hibás", + "Wrong Recovery Key": "A Visszaállítási Kulcs hibás", + "Invalid Recovery Key": "A Visszaállítási Kulcs hibás", + "Security Phrase": "Biztonsági jelmondat", + "Enter your Security Phrase or to continue.": "Add meg a Biztonsági jelmondatot vagy a folytatáshoz.", + "Security Key": "Biztonsági Kulcs", + "Use your Security Key to continue.": "Használd a Biztonsági Kulcsot a folytatáshoz.", + "Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.": "A titkosított üzenetekhez és adatokhoz való hozzáférés elvesztése esetén használható biztonsági tartalék a titkosított kulcsok a szerveredre való elmentésével.", + "Generate a Security Key": "Biztonsági Kulcs elkészítése", + "We’ll generate a Security Key for you to store somewhere safe, like a password manager or a safe.": "A Biztonsági Kulcsodat elkészítjük neked amit tárolj valamilyen biztonságos helyen mint pl. a jelszókezelő vagy széf.", + "Enter a Security Phrase": "Biztonsági Jelmondat megadása", + "Use a secret phrase only you know, and optionally save a Security Key to use for backup.": "Olyan biztonsági jelmondatot használj amit csak te ismersz és esetleg mentsd el a Biztonsági Kulcsot vésztartaléknak.", + "Enter a security phrase only you know, as it’s used to safeguard your data. To be secure, you shouldn’t re-use your account password.": "Olyan biztonsági jelmondatot adj meg amit csak te ismersz, mert ez fogja az adataidat őrizni. Hogy biztonságos legyen ne használd a fiókod jelszavát.", + "Store your Security Key somewhere safe, like a password manager or a safe, as it’s used to safeguard your encrypted data.": "A Biztonsági Kulcsot tárold biztonságos helyen, mint pl. a jelszókezelő vagy széf, mivel ez tartja biztonságban a titkosított adataidat.", + "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.": "Ha most megszakítod, akkor a munkameneteidhez való hozzáférés elvesztésével elveszítheted a titkosított üzeneteidet és adataidat.", + "You can also set up Secure Backup & manage your keys in Settings.": "A Biztonsági mentést és a kulcsok kezelését beállíthatod a Beállításokban.", + "Set up Secure backup": "Biztonsági mentés beállítása", + "Set a Security Phrase": "Biztonsági Jelmondat beállítása", + "Confirm Security Phrase": "Biztonsági Jelmondat megerősítése", + "Save your Security Key": "Ments el a Biztonsági Kulcsodat" } From 393eaf84c385d9208aad5f188cba5d747bab9c4e Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 30 Jun 2020 13:34:44 -0600 Subject: [PATCH 202/294] Move notification states out of the NotificationBadge component Fixes https://github.com/vector-im/riot-web/issues/14153 --- .../views/rooms/NotificationBadge.tsx | 269 +----------------- src/components/views/rooms/RoomSublist2.tsx | 3 +- src/components/views/rooms/RoomTile2.tsx | 9 +- .../notifications/INotificationState.ts | 26 ++ .../notifications/ListNotificationState.ts | 120 ++++++++ src/stores/notifications/NotificationColor.ts | 24 ++ .../notifications/RoomNotificationState.ts | 131 +++++++++ .../notifications/StaticNotificationState.ts | 33 +++ .../TagSpecificNotificationState.ts | 45 +++ 9 files changed, 387 insertions(+), 273 deletions(-) create mode 100644 src/stores/notifications/INotificationState.ts create mode 100644 src/stores/notifications/ListNotificationState.ts create mode 100644 src/stores/notifications/NotificationColor.ts create mode 100644 src/stores/notifications/RoomNotificationState.ts create mode 100644 src/stores/notifications/StaticNotificationState.ts create mode 100644 src/stores/notifications/TagSpecificNotificationState.ts diff --git a/src/components/views/rooms/NotificationBadge.tsx b/src/components/views/rooms/NotificationBadge.tsx index 2111310555..1bb72d02c3 100644 --- a/src/components/views/rooms/NotificationBadge.tsx +++ b/src/components/views/rooms/NotificationBadge.tsx @@ -17,35 +17,9 @@ limitations under the License. import React from "react"; import classNames from "classnames"; import { formatMinimalBadgeCount } from "../../../utils/FormattingUtils"; -import { Room } from "matrix-js-sdk/src/models/room"; -import * as RoomNotifs from '../../../RoomNotifs'; -import { EffectiveMembership, getEffectiveMembership } from "../../../stores/room-list/membership"; -import * as Unread from '../../../Unread'; -import { MatrixClientPeg } from "../../../MatrixClientPeg"; -import { MatrixEvent } from "matrix-js-sdk/src/models/event"; -import { EventEmitter } from "events"; -import { arrayDiff } from "../../../utils/arrays"; -import { IDestroyable } from "../../../utils/IDestroyable"; import SettingsStore from "../../../settings/SettingsStore"; -import { DefaultTagID, TagID } from "../../../stores/room-list/models"; -import { readReceiptChangeIsFor } from "../../../utils/read-receipts"; - -export const NOTIFICATION_STATE_UPDATE = "update"; - -export enum NotificationColor { - // Inverted (None -> Red) because we do integer comparisons on this - None, // nothing special - // TODO: Remove bold with notifications: https://github.com/vector-im/riot-web/issues/14227 - Bold, // no badge, show as unread - Grey, // unread notified messages - Red, // unread pings -} - -export interface INotificationState extends EventEmitter { - symbol?: string; - count: number; - color: NotificationColor; -} +import { INotificationState, NOTIFICATION_STATE_UPDATE } from "../../../stores/notifications/INotificationState"; +import { NotificationColor } from "../../../stores/notifications/NotificationColor"; interface IProps { notification: INotificationState; @@ -141,242 +115,3 @@ export default class NotificationBadge extends React.PureComponent { - if (!readReceiptChangeIsFor(event, MatrixClientPeg.get())) return; // not our own - ignore - if (room.roomId !== this.room.roomId) return; // not for us - ignore - this.updateNotificationState(); - }; - - private handleRoomEventUpdate = (event: MatrixEvent) => { - const roomId = event.getRoomId(); - - if (roomId !== this.room.roomId) return; // ignore - not for us - this.updateNotificationState(); - }; - - private updateNotificationState() { - const before = {count: this.count, symbol: this.symbol, color: this.color}; - - if (this.roomIsInvite) { - this._color = NotificationColor.Red; - this._symbol = "!"; - this._count = 1; // not used, technically - } else { - const redNotifs = RoomNotifs.getUnreadNotificationCount(this.room, 'highlight'); - const greyNotifs = RoomNotifs.getUnreadNotificationCount(this.room, 'total'); - - // For a 'true count' we pick the grey notifications first because they include the - // red notifications. If we don't have a grey count for some reason we use the red - // count. If that count is broken for some reason, assume zero. This avoids us showing - // a badge for 'NaN' (which formats as 'NaNB' for NaN Billion). - const trueCount = greyNotifs ? greyNotifs : (redNotifs ? redNotifs : 0); - - // Note: we only set the symbol if we have an actual count. We don't want to show - // zero on badges. - - if (redNotifs > 0) { - this._color = NotificationColor.Red; - this._count = trueCount; - this._symbol = null; // symbol calculated by component - } else if (greyNotifs > 0) { - this._color = NotificationColor.Grey; - this._count = trueCount; - this._symbol = null; // symbol calculated by component - } else { - // We don't have any notified messages, but we might have unread messages. Let's - // find out. - const hasUnread = Unread.doesRoomHaveUnreadMessages(this.room); - if (hasUnread) { - this._color = NotificationColor.Bold; - } else { - this._color = NotificationColor.None; - } - - // no symbol or count for this state - this._count = 0; - this._symbol = null; - } - } - - // finally, publish an update if needed - const after = {count: this.count, symbol: this.symbol, color: this.color}; - if (JSON.stringify(before) !== JSON.stringify(after)) { - this.emit(NOTIFICATION_STATE_UPDATE); - } - } -} - -export class TagSpecificNotificationState extends RoomNotificationState { - private static TAG_TO_COLOR: { - // @ts-ignore - TS wants this to be a string key, but we know better - [tagId: TagID]: NotificationColor, - } = { - [DefaultTagID.DM]: NotificationColor.Red, - }; - - private readonly colorWhenNotIdle?: NotificationColor; - - constructor(room: Room, tagId: TagID) { - super(room); - - const specificColor = TagSpecificNotificationState.TAG_TO_COLOR[tagId]; - if (specificColor) this.colorWhenNotIdle = specificColor; - } - - public get color(): NotificationColor { - if (!this.colorWhenNotIdle) return super.color; - - if (super.color !== NotificationColor.None) return this.colorWhenNotIdle; - return super.color; - } -} - -export class ListNotificationState extends EventEmitter implements IDestroyable, INotificationState { - private _count: number; - private _color: NotificationColor; - private rooms: Room[] = []; - private states: { [roomId: string]: RoomNotificationState } = {}; - - constructor(private byTileCount = false, private tagId: TagID) { - super(); - } - - public get symbol(): string { - return null; // This notification state doesn't support symbols - } - - public get count(): number { - return this._count; - } - - public get color(): NotificationColor { - return this._color; - } - - public setRooms(rooms: Room[]) { - // If we're only concerned about the tile count, don't bother setting up listeners. - if (this.byTileCount) { - this.rooms = rooms; - this.calculateTotalState(); - return; - } - - const oldRooms = this.rooms; - const diff = arrayDiff(oldRooms, rooms); - this.rooms = rooms; - for (const oldRoom of diff.removed) { - const state = this.states[oldRoom.roomId]; - if (!state) continue; // We likely just didn't have a badge (race condition) - delete this.states[oldRoom.roomId]; - state.off(NOTIFICATION_STATE_UPDATE, this.onRoomNotificationStateUpdate); - state.destroy(); - } - for (const newRoom of diff.added) { - const state = new TagSpecificNotificationState(newRoom, this.tagId); - state.on(NOTIFICATION_STATE_UPDATE, this.onRoomNotificationStateUpdate); - if (this.states[newRoom.roomId]) { - // "Should never happen" disclaimer. - console.warn("Overwriting notification state for room:", newRoom.roomId); - this.states[newRoom.roomId].destroy(); - } - this.states[newRoom.roomId] = state; - } - - this.calculateTotalState(); - } - - public getForRoom(room: Room) { - const state = this.states[room.roomId]; - if (!state) throw new Error("Unknown room for notification state"); - return state; - } - - public destroy() { - for (const state of Object.values(this.states)) { - state.destroy(); - } - this.states = {}; - } - - private onRoomNotificationStateUpdate = () => { - this.calculateTotalState(); - }; - - private calculateTotalState() { - const before = {count: this.count, symbol: this.symbol, color: this.color}; - - if (this.byTileCount) { - this._color = NotificationColor.Red; - this._count = this.rooms.length; - } else { - this._count = 0; - this._color = NotificationColor.None; - for (const state of Object.values(this.states)) { - this._count += state.count; - this._color = Math.max(this.color, state.color); - } - } - - // finally, publish an update if needed - const after = {count: this.count, symbol: this.symbol, color: this.color}; - if (JSON.stringify(before) !== JSON.stringify(after)) { - this.emit(NOTIFICATION_STATE_UPDATE); - } - } -} diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index 58ebf54bf7..fadecbd0d6 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -26,13 +26,14 @@ import AccessibleButton from "../../views/elements/AccessibleButton"; import RoomTile2 from "./RoomTile2"; import { ResizableBox, ResizeCallbackData } from "react-resizable"; import { ListLayout } from "../../../stores/room-list/ListLayout"; -import NotificationBadge, { ListNotificationState } from "./NotificationBadge"; import { ContextMenu, ContextMenuButton } from "../../structures/ContextMenu"; import StyledCheckbox from "../elements/StyledCheckbox"; import StyledRadioButton from "../elements/StyledRadioButton"; import RoomListStore from "../../../stores/room-list/RoomListStore2"; import { ListAlgorithm, SortAlgorithm } from "../../../stores/room-list/algorithms/models"; import { TagID } from "../../../stores/room-list/models"; +import { ListNotificationState } from "../../../stores/notifications/ListNotificationState"; +import NotificationBadge from "./NotificationBadge"; // 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 diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index 1284728855..4c9147e005 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -26,16 +26,15 @@ import RoomAvatar from "../../views/avatars/RoomAvatar"; import dis from '../../../dispatcher/dispatcher'; import { Key } from "../../../Keyboard"; import ActiveRoomObserver from "../../../ActiveRoomObserver"; -import NotificationBadge, { - INotificationState, - NotificationColor, - TagSpecificNotificationState -} from "./NotificationBadge"; import { _t } from "../../../languageHandler"; import { ContextMenu, ContextMenuButton } from "../../structures/ContextMenu"; import { DefaultTagID, TagID } from "../../../stores/room-list/models"; import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore"; import RoomTileIcon from "./RoomTileIcon"; +import { TagSpecificNotificationState } from "../../../stores/notifications/TagSpecificNotificationState"; +import { INotificationState } from "../../../stores/notifications/INotificationState"; +import NotificationBadge from "./NotificationBadge"; +import { NotificationColor } from "../../../stores/notifications/NotificationColor"; // 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 diff --git a/src/stores/notifications/INotificationState.ts b/src/stores/notifications/INotificationState.ts new file mode 100644 index 0000000000..65bd7b7957 --- /dev/null +++ b/src/stores/notifications/INotificationState.ts @@ -0,0 +1,26 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { EventEmitter } from "events"; +import { NotificationColor } from "./NotificationColor"; + +export const NOTIFICATION_STATE_UPDATE = "update"; + +export interface INotificationState extends EventEmitter { + symbol?: string; + count: number; + color: NotificationColor; +} diff --git a/src/stores/notifications/ListNotificationState.ts b/src/stores/notifications/ListNotificationState.ts new file mode 100644 index 0000000000..5773693b47 --- /dev/null +++ b/src/stores/notifications/ListNotificationState.ts @@ -0,0 +1,120 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { EventEmitter } from "events"; +import { INotificationState, NOTIFICATION_STATE_UPDATE } from "./INotificationState"; +import { NotificationColor } from "./NotificationColor"; +import { IDestroyable } from "../../utils/IDestroyable"; +import { TagID } from "../room-list/models"; +import { Room } from "matrix-js-sdk/src/models/room"; +import { arrayDiff } from "../../utils/arrays"; +import { RoomNotificationState } from "./RoomNotificationState"; +import { TagSpecificNotificationState } from "./TagSpecificNotificationState"; + +export class ListNotificationState extends EventEmitter implements IDestroyable, INotificationState { + private _count: number; + private _color: NotificationColor; + private rooms: Room[] = []; + private states: { [roomId: string]: RoomNotificationState } = {}; + + constructor(private byTileCount = false, private tagId: TagID) { + super(); + } + + public get symbol(): string { + return null; // This notification state doesn't support symbols + } + + public get count(): number { + return this._count; + } + + public get color(): NotificationColor { + return this._color; + } + + public setRooms(rooms: Room[]) { + // If we're only concerned about the tile count, don't bother setting up listeners. + if (this.byTileCount) { + this.rooms = rooms; + this.calculateTotalState(); + return; + } + + const oldRooms = this.rooms; + const diff = arrayDiff(oldRooms, rooms); + this.rooms = rooms; + for (const oldRoom of diff.removed) { + const state = this.states[oldRoom.roomId]; + if (!state) continue; // We likely just didn't have a badge (race condition) + delete this.states[oldRoom.roomId]; + state.off(NOTIFICATION_STATE_UPDATE, this.onRoomNotificationStateUpdate); + state.destroy(); + } + for (const newRoom of diff.added) { + const state = new TagSpecificNotificationState(newRoom, this.tagId); + state.on(NOTIFICATION_STATE_UPDATE, this.onRoomNotificationStateUpdate); + if (this.states[newRoom.roomId]) { + // "Should never happen" disclaimer. + console.warn("Overwriting notification state for room:", newRoom.roomId); + this.states[newRoom.roomId].destroy(); + } + this.states[newRoom.roomId] = state; + } + + this.calculateTotalState(); + } + + public getForRoom(room: Room) { + const state = this.states[room.roomId]; + if (!state) throw new Error("Unknown room for notification state"); + return state; + } + + public destroy() { + for (const state of Object.values(this.states)) { + state.destroy(); + } + this.states = {}; + } + + private onRoomNotificationStateUpdate = () => { + this.calculateTotalState(); + }; + + private calculateTotalState() { + const before = {count: this.count, symbol: this.symbol, color: this.color}; + + if (this.byTileCount) { + this._color = NotificationColor.Red; + this._count = this.rooms.length; + } else { + this._count = 0; + this._color = NotificationColor.None; + for (const state of Object.values(this.states)) { + this._count += state.count; + this._color = Math.max(this.color, state.color); + } + } + + // finally, publish an update if needed + const after = {count: this.count, symbol: this.symbol, color: this.color}; + if (JSON.stringify(before) !== JSON.stringify(after)) { + this.emit(NOTIFICATION_STATE_UPDATE); + } + } +} + diff --git a/src/stores/notifications/NotificationColor.ts b/src/stores/notifications/NotificationColor.ts new file mode 100644 index 0000000000..aa2384b3df --- /dev/null +++ b/src/stores/notifications/NotificationColor.ts @@ -0,0 +1,24 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +export enum NotificationColor { + // Inverted (None -> Red) because we do integer comparisons on this + None, // nothing special + // TODO: Remove bold with notifications: https://github.com/vector-im/riot-web/issues/14227 + Bold, // no badge, show as unread + Grey, // unread notified messages + Red, // unread pings +} diff --git a/src/stores/notifications/RoomNotificationState.ts b/src/stores/notifications/RoomNotificationState.ts new file mode 100644 index 0000000000..b9bc3f3492 --- /dev/null +++ b/src/stores/notifications/RoomNotificationState.ts @@ -0,0 +1,131 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { EventEmitter } from "events"; +import { INotificationState, NOTIFICATION_STATE_UPDATE } from "./INotificationState"; +import { NotificationColor } from "./NotificationColor"; +import { IDestroyable } from "../../utils/IDestroyable"; +import { MatrixClientPeg } from "../../MatrixClientPeg"; +import { EffectiveMembership, getEffectiveMembership } from "../room-list/membership"; +import { readReceiptChangeIsFor } from "../../utils/read-receipts"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { Room } from "matrix-js-sdk/src/models/room"; +import * as RoomNotifs from '../../RoomNotifs'; +import * as Unread from '../../Unread'; + +export class RoomNotificationState extends EventEmitter implements IDestroyable, INotificationState { + private _symbol: string; + private _count: number; + private _color: NotificationColor; + + constructor(private room: Room) { + super(); + this.room.on("Room.receipt", this.handleReadReceipt); + this.room.on("Room.timeline", this.handleRoomEventUpdate); + this.room.on("Room.redaction", this.handleRoomEventUpdate); + MatrixClientPeg.get().on("Event.decrypted", this.handleRoomEventUpdate); + this.updateNotificationState(); + } + + public get symbol(): string { + return this._symbol; + } + + public get count(): number { + return this._count; + } + + public get color(): NotificationColor { + return this._color; + } + + private get roomIsInvite(): boolean { + return getEffectiveMembership(this.room.getMyMembership()) === EffectiveMembership.Invite; + } + + public destroy(): void { + this.room.removeListener("Room.receipt", this.handleReadReceipt); + this.room.removeListener("Room.timeline", this.handleRoomEventUpdate); + this.room.removeListener("Room.redaction", this.handleRoomEventUpdate); + if (MatrixClientPeg.get()) { + MatrixClientPeg.get().removeListener("Event.decrypted", this.handleRoomEventUpdate); + } + } + + private handleReadReceipt = (event: MatrixEvent, room: Room) => { + if (!readReceiptChangeIsFor(event, MatrixClientPeg.get())) return; // not our own - ignore + if (room.roomId !== this.room.roomId) return; // not for us - ignore + this.updateNotificationState(); + }; + + private handleRoomEventUpdate = (event: MatrixEvent) => { + const roomId = event.getRoomId(); + + if (roomId !== this.room.roomId) return; // ignore - not for us + this.updateNotificationState(); + }; + + private updateNotificationState() { + const before = {count: this.count, symbol: this.symbol, color: this.color}; + + if (this.roomIsInvite) { + this._color = NotificationColor.Red; + this._symbol = "!"; + this._count = 1; // not used, technically + } else { + const redNotifs = RoomNotifs.getUnreadNotificationCount(this.room, 'highlight'); + const greyNotifs = RoomNotifs.getUnreadNotificationCount(this.room, 'total'); + + // For a 'true count' we pick the grey notifications first because they include the + // red notifications. If we don't have a grey count for some reason we use the red + // count. If that count is broken for some reason, assume zero. This avoids us showing + // a badge for 'NaN' (which formats as 'NaNB' for NaN Billion). + const trueCount = greyNotifs ? greyNotifs : (redNotifs ? redNotifs : 0); + + // Note: we only set the symbol if we have an actual count. We don't want to show + // zero on badges. + + if (redNotifs > 0) { + this._color = NotificationColor.Red; + this._count = trueCount; + this._symbol = null; // symbol calculated by component + } else if (greyNotifs > 0) { + this._color = NotificationColor.Grey; + this._count = trueCount; + this._symbol = null; // symbol calculated by component + } else { + // We don't have any notified messages, but we might have unread messages. Let's + // find out. + const hasUnread = Unread.doesRoomHaveUnreadMessages(this.room); + if (hasUnread) { + this._color = NotificationColor.Bold; + } else { + this._color = NotificationColor.None; + } + + // no symbol or count for this state + this._count = 0; + this._symbol = null; + } + } + + // finally, publish an update if needed + const after = {count: this.count, symbol: this.symbol, color: this.color}; + if (JSON.stringify(before) !== JSON.stringify(after)) { + this.emit(NOTIFICATION_STATE_UPDATE); + } + } +} diff --git a/src/stores/notifications/StaticNotificationState.ts b/src/stores/notifications/StaticNotificationState.ts new file mode 100644 index 0000000000..51902688fe --- /dev/null +++ b/src/stores/notifications/StaticNotificationState.ts @@ -0,0 +1,33 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { EventEmitter } from "events"; +import { INotificationState } from "./INotificationState"; +import { NotificationColor } from "./NotificationColor"; + +export class StaticNotificationState extends EventEmitter implements INotificationState { + constructor(public symbol: string, public count: number, public color: NotificationColor) { + super(); + } + + public static forCount(count: number, color: NotificationColor): StaticNotificationState { + return new StaticNotificationState(null, count, color); + } + + public static forSymbol(symbol: string, color: NotificationColor): StaticNotificationState { + return new StaticNotificationState(symbol, 0, color); + } +} diff --git a/src/stores/notifications/TagSpecificNotificationState.ts b/src/stores/notifications/TagSpecificNotificationState.ts new file mode 100644 index 0000000000..02d8717fee --- /dev/null +++ b/src/stores/notifications/TagSpecificNotificationState.ts @@ -0,0 +1,45 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { NotificationColor } from "./NotificationColor"; +import { Room } from "matrix-js-sdk/src/models/room"; +import { DefaultTagID, TagID } from "../room-list/models"; +import { RoomNotificationState } from "./RoomNotificationState"; + +export class TagSpecificNotificationState extends RoomNotificationState { + private static TAG_TO_COLOR: { + // @ts-ignore - TS wants this to be a string key, but we know better + [tagId: TagID]: NotificationColor, + } = { + [DefaultTagID.DM]: NotificationColor.Red, + }; + + private readonly colorWhenNotIdle?: NotificationColor; + + constructor(room: Room, tagId: TagID) { + super(room); + + const specificColor = TagSpecificNotificationState.TAG_TO_COLOR[tagId]; + if (specificColor) this.colorWhenNotIdle = specificColor; + } + + public get color(): NotificationColor { + if (!this.colorWhenNotIdle) return super.color; + + if (super.color !== NotificationColor.None) return this.colorWhenNotIdle; + return super.color; + } +} From 782a555e44de49626837604020b05d1ad47b5a25 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 30 Jun 2020 13:45:10 -0600 Subject: [PATCH 203/294] Make badges represent old list behaviour Fixes https://github.com/vector-im/riot-web/issues/14160 --- src/components/views/rooms/NotificationBadge.tsx | 9 ++++++--- src/stores/notifications/TagSpecificNotificationState.ts | 5 +++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/views/rooms/NotificationBadge.tsx b/src/components/views/rooms/NotificationBadge.tsx index 1bb72d02c3..dfc02176c6 100644 --- a/src/components/views/rooms/NotificationBadge.tsx +++ b/src/components/views/rooms/NotificationBadge.tsx @@ -86,11 +86,14 @@ export default class NotificationBadge extends React.PureComponent= NotificationColor.Red; const hasCount = this.props.notification.color >= NotificationColor.Grey; - const hasUnread = this.props.notification.color >= NotificationColor.Bold; - const couldBeEmpty = (!this.state.showCounts || hasUnread) && !hasNotif; - let isEmptyBadge = couldBeEmpty && (!this.state.showCounts || !hasCount); + const hasAnySymbol = this.props.notification.symbol || this.props.notification.count > 0; + let isEmptyBadge = !hasAnySymbol || !hasCount; if (this.props.forceCount) { isEmptyBadge = false; if (!hasCount) return null; // Can't render a badge diff --git a/src/stores/notifications/TagSpecificNotificationState.ts b/src/stores/notifications/TagSpecificNotificationState.ts index 02d8717fee..b443f4633b 100644 --- a/src/stores/notifications/TagSpecificNotificationState.ts +++ b/src/stores/notifications/TagSpecificNotificationState.ts @@ -16,7 +16,7 @@ limitations under the License. import { NotificationColor } from "./NotificationColor"; import { Room } from "matrix-js-sdk/src/models/room"; -import { DefaultTagID, TagID } from "../room-list/models"; +import { TagID } from "../room-list/models"; import { RoomNotificationState } from "./RoomNotificationState"; export class TagSpecificNotificationState extends RoomNotificationState { @@ -24,7 +24,8 @@ export class TagSpecificNotificationState extends RoomNotificationState { // @ts-ignore - TS wants this to be a string key, but we know better [tagId: TagID]: NotificationColor, } = { - [DefaultTagID.DM]: NotificationColor.Red, + // TODO: Update for FTUE Notifications: https://github.com/vector-im/riot-web/issues/14261 + //[DefaultTagID.DM]: NotificationColor.Red, }; private readonly colorWhenNotIdle?: NotificationColor; From c7a83e65f0487964c8597699951525cf28d17766 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 24 Jun 2020 21:31:44 -0600 Subject: [PATCH 204/294] Fix proliferation when joining upgraded rooms We have to do a bit of a dance to return the sticky room to the list so we can remove it, if needed, and ensure that we generally swap the rooms out of the list. --- src/stores/room-list/RoomListStore2.ts | 43 +++++++++--- src/stores/room-list/algorithms/Algorithm.ts | 74 +++++++++++++++++--- 2 files changed, 98 insertions(+), 19 deletions(-) diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index 58a78f4dd8..b4b643687d 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -97,8 +97,10 @@ export class RoomListStore2 extends AsyncStore { this.algorithm.stickyRoom = null; } else if (activeRoomId) { const activeRoom = this.matrixClient.getRoom(activeRoomId); - if (!activeRoom) throw new Error(`${activeRoomId} is current in RVS but missing from client`); - if (activeRoom !== this.algorithm.stickyRoom) { + if (!activeRoom) { + 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; @@ -187,10 +189,13 @@ export class RoomListStore2 extends AsyncStore { console.log(`[RoomListDebug] Live timeline event ${eventPayload.event.getId()} in ${updatedRoom.roomId}`); if (eventPayload.event.getType() === 'm.room.tombstone' && eventPayload.event.getStateKey() === '') { // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 - console.log(`[RoomListDebug] Got tombstone event - regenerating room list`); - // TODO: We could probably be smarter about this: https://github.com/vector-im/riot-web/issues/14035 - await this.regenerateAllLists(); - return; // don't pass the update down - we will have already handled it in the regen + console.log(`[RoomListDebug] Got tombstone event - trying to remove now-dead room`); + const newRoom = this.matrixClient.getRoom(eventPayload.event.getContent()['replacement_room']); + if (newRoom) { + // If we have the new room, then the new room check will have seen the predecessor + // and did the required updates, so do nothing here. + return; + } } await this.handleRoomUpdate(updatedRoom, RoomUpdateCause.Timeline); }; @@ -245,7 +250,29 @@ export class RoomListStore2 extends AsyncStore { if (membershipPayload.oldMembership !== "join" && membershipPayload.membership === "join") { // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 console.log(`[RoomListDebug] Handling new room ${membershipPayload.room.roomId}`); - await this.algorithm.handleRoomUpdate(membershipPayload.room, RoomUpdateCause.NewRoom); + + // If we're joining an upgraded room, we'll want to make sure we don't proliferate + // the dead room in the list. + const createEvent = membershipPayload.room.currentState.getStateEvents("m.room.create", ""); + if (createEvent && createEvent.getContent()['predecessor']) { + console.log(`[RoomListDebug] Room has a predecessor`); + const prevRoom = this.matrixClient.getRoom(createEvent.getContent()['predecessor']['room_id']); + if (prevRoom) { + const isSticky = this.algorithm.stickyRoom === prevRoom; + if (isSticky) { + console.log(`[RoomListDebug] Clearing sticky room due to room upgrade`); + await this.algorithm.setStickyRoomAsync(null); + } + + // Note: we hit the algorithm instead of our handleRoomUpdate() function to + // avoid redundant updates. + console.log(`[RoomListDebug] Removing previous room from room list`); + await this.algorithm.handleRoomUpdate(prevRoom, RoomUpdateCause.RoomRemoved); + } + } + + console.log(`[RoomListDebug] Adding new room to room list`); + await this.handleRoomUpdate(membershipPayload.room, RoomUpdateCause.NewRoom); return; } @@ -253,7 +280,7 @@ export class RoomListStore2 extends AsyncStore { if (membershipPayload.oldMembership !== membershipPayload.membership) { // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 console.log(`[RoomListDebug] Handling membership change in ${membershipPayload.room.roomId}`); - await this.algorithm.handleRoomUpdate(membershipPayload.room, RoomUpdateCause.PossibleTagChange); + await this.handleRoomUpdate(membershipPayload.room, RoomUpdateCause.PossibleTagChange); return; } } diff --git a/src/stores/room-list/algorithms/Algorithm.ts b/src/stores/room-list/algorithms/Algorithm.ts index 37ce7e4ba7..4fbc576dfa 100644 --- a/src/stores/room-list/algorithms/Algorithm.ts +++ b/src/stores/room-list/algorithms/Algorithm.ts @@ -30,7 +30,7 @@ import { SortAlgorithm } from "./models"; import { FILTER_CHANGED, FilterPriority, IFilterCondition } from "../filters/IFilterCondition"; -import { EffectiveMembership, splitRoomsByMembership } from "../membership"; +import { EffectiveMembership, getEffectiveMembership, splitRoomsByMembership } from "../membership"; import { OrderingAlgorithm } from "./list-ordering/OrderingAlgorithm"; import { getListAlgorithmInstance } from "./list-ordering"; @@ -99,6 +99,14 @@ export class Algorithm extends EventEmitter { return this._cachedRooms; } + /** + * Awaitable version of the sticky room setter. + * @param val The new room to sticky. + */ + public async setStickyRoomAsync(val: Room) { + await this.updateStickyRoom(val); + } + public getTagSorting(tagId: TagID): SortAlgorithm { return this.sortAlgorithms[tagId]; } @@ -160,10 +168,13 @@ export class Algorithm extends EventEmitter { // It's possible to have no selected room. In that case, clear the sticky room if (!val) { if (this._stickyRoom) { + const stickyRoom = this._stickyRoom.room; + this._stickyRoom = null; // clear before we go to update the algorithm + // Lie to the algorithm and re-add the room to the algorithm - await this.handleRoomUpdate(this._stickyRoom.room, RoomUpdateCause.NewRoom); + await this.handleRoomUpdate(stickyRoom, RoomUpdateCause.NewRoom); + return; } - this._stickyRoom = null; return; } @@ -289,6 +300,8 @@ export class Algorithm extends EventEmitter { } protected recalculateFilteredRoomsForTag(tagId: TagID): void { + if (!this.hasFilters) return; // don't bother doing work if there's nothing to do + console.log(`Recalculating filtered rooms for ${tagId}`); delete this.filteredRooms[tagId]; const rooms = this.cachedRooms[tagId].map(r => r); // cheap clone @@ -458,14 +471,7 @@ export class Algorithm extends EventEmitter { // Now process all the joined rooms. This is a bit more complicated for (const room of memberships[EffectiveMembership.Join]) { - let tags = Object.keys(room.tags || {}); - - if (tags.length === 0) { - // Check to see if it's a DM if it isn't anything else - if (DMRoomMap.shared().getUserIdForRoomId(room.roomId)) { - tags = [DefaultTagID.DM]; - } - } + const tags = this.getTagsOfJoinedRoom(room); let inTag = false; if (tags.length > 0) { @@ -496,6 +502,39 @@ export class Algorithm extends EventEmitter { this.updateTagsFromCache(); } + private getTagsForRoom(room: Room): TagID[] { + // XXX: This duplicates a lot of logic from setKnownRooms above, but has a slightly + // different use case and therefore different performance curve + + const tags: TagID[] = []; + + const membership = getEffectiveMembership(room.getMyMembership()); + if (membership === EffectiveMembership.Invite) { + tags.push(DefaultTagID.Invite); + } else if (membership === EffectiveMembership.Leave) { + tags.push(DefaultTagID.Archived); + } else { + tags.push(...this.getTagsOfJoinedRoom(room)); + } + + if (!tags.length) tags.push(DefaultTagID.Untagged); + + return tags; + } + + private getTagsOfJoinedRoom(room: Room): TagID[] { + let tags = Object.keys(room.tags || {}); + + if (tags.length === 0) { + // Check to see if it's a DM if it isn't anything else + if (DMRoomMap.shared().getUserIdForRoomId(room.roomId)) { + tags = [DefaultTagID.DM]; + } + } + + return tags; + } + /** * Updates the roomsToTags map */ @@ -566,6 +605,19 @@ export class Algorithm extends EventEmitter { } } + if (cause === RoomUpdateCause.NewRoom && !this.roomIdsToTags[room.roomId]) { + console.log(`[RoomListDebug] Updating tags for new room ${room.roomId} (${room.name})`); + + // Get the tags for the room and populate the cache + const roomTags = this.getTagsForRoom(room).filter(t => !isNullOrUndefined(this.cachedRooms[t])); + + // "This should never happen" condition - we specify DefaultTagID.Untagged in getTagsForRoom(), + // which means we should *always* have a tag to go off of. + if (!roomTags.length) throw new Error(`Tags cannot be determined for ${room.roomId}`); + + this.roomIdsToTags[room.roomId] = roomTags; + } + let tags = this.roomIdsToTags[room.roomId]; if (!tags) { console.warn(`No tags known for "${room.name}" (${room.roomId})`); From 223ee0dbdb9efd779c7082ec066405c2b6992f6a Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 24 Jun 2020 21:41:11 -0600 Subject: [PATCH 205/294] Add locking to avoid index corruption When a new room is added there's a fairly good chance that the other events being dispatched will happen in the middle of (for example) the room list being re-sorted. This commit wraps the entire handleRoomUpdate() function for the underlying algorithms in a lock so that if we're unlucky enough to get an update while we're sorting (as the ImportanceAlgorithm splices out what it is sorting) we won't scream about invalid index errors. --- .../list-ordering/ImportanceAlgorithm.ts | 82 ++++++++++--------- .../list-ordering/NaturalAlgorithm.ts | 40 +++++---- .../list-ordering/OrderingAlgorithm.ts | 2 + 3 files changed, 69 insertions(+), 55 deletions(-) diff --git a/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts b/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts index e2a9fc1952..e95f92f985 100644 --- a/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts +++ b/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts @@ -179,45 +179,51 @@ export class ImportanceAlgorithm extends OrderingAlgorithm { } public async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise { - if (cause === RoomUpdateCause.NewRoom || cause === RoomUpdateCause.RoomRemoved) { - return this.handleSplice(room, cause); + try { + await this.updateLock.acquireAsync(); + + if (cause === RoomUpdateCause.NewRoom || cause === RoomUpdateCause.RoomRemoved) { + return this.handleSplice(room, cause); + } + + if (cause !== RoomUpdateCause.Timeline && cause !== RoomUpdateCause.ReadReceipt) { + throw new Error(`Unsupported update cause: ${cause}`); + } + + const category = this.getRoomCategory(room); + if (this.sortingAlgorithm === SortAlgorithm.Manual) { + return; // Nothing to do here. + } + + const roomIdx = this.getRoomIndex(room); + if (roomIdx === -1) { + throw new Error(`Room ${room.roomId} has no index in ${this.tagId}`); + } + + // Try to avoid doing array operations if we don't have to: only move rooms within + // the categories if we're jumping categories + const oldCategory = this.getCategoryFromIndices(roomIdx, this.indices); + if (oldCategory !== category) { + // Move the room and update the indices + this.moveRoomIndexes(1, oldCategory, category, this.indices); + this.cachedOrderedRooms.splice(roomIdx, 1); // splice out the old index (fixed position) + this.cachedOrderedRooms.splice(this.indices[category], 0, room); // splice in the new room (pre-adjusted) + // Note: if moveRoomIndexes() is called after the splice then the insert operation + // will happen in the wrong place. Because we would have already adjusted the index + // for the category, we don't need to determine how the room is moving in the list. + // If we instead tried to insert before updating the indices, we'd have to determine + // whether the room was moving later (towards IDLE) or earlier (towards RED) from its + // current position, as it'll affect the category's start index after we remove the + // room from the array. + } + + // Sort the category now that we've dumped the room in + await this.sortCategory(category); + + return true; // change made + } finally { + await this.updateLock.release(); } - - if (cause !== RoomUpdateCause.Timeline && cause !== RoomUpdateCause.ReadReceipt) { - throw new Error(`Unsupported update cause: ${cause}`); - } - - const category = this.getRoomCategory(room); - if (this.sortingAlgorithm === SortAlgorithm.Manual) { - return; // Nothing to do here. - } - - const roomIdx = this.getRoomIndex(room); - if (roomIdx === -1) { - throw new Error(`Room ${room.roomId} has no index in ${this.tagId}`); - } - - // Try to avoid doing array operations if we don't have to: only move rooms within - // the categories if we're jumping categories - const oldCategory = this.getCategoryFromIndices(roomIdx, this.indices); - if (oldCategory !== category) { - // Move the room and update the indices - this.moveRoomIndexes(1, oldCategory, category, this.indices); - this.cachedOrderedRooms.splice(roomIdx, 1); // splice out the old index (fixed position) - this.cachedOrderedRooms.splice(this.indices[category], 0, room); // splice in the new room (pre-adjusted) - // Note: if moveRoomIndexes() is called after the splice then the insert operation - // will happen in the wrong place. Because we would have already adjusted the index - // for the category, we don't need to determine how the room is moving in the list. - // If we instead tried to insert before updating the indices, we'd have to determine - // whether the room was moving later (towards IDLE) or earlier (towards RED) from its - // current position, as it'll affect the category's start index after we remove the - // room from the array. - } - - // Sort the category now that we've dumped the room in - await this.sortCategory(category); - - return true; // change made } private async sortCategory(category: Category) { diff --git a/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts b/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts index 79fa2ed604..f74329cb4d 100644 --- a/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts +++ b/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts @@ -38,23 +38,29 @@ export class NaturalAlgorithm extends OrderingAlgorithm { } public async handleRoomUpdate(room, cause): Promise { - const isSplice = cause === RoomUpdateCause.NewRoom || cause === RoomUpdateCause.RoomRemoved; - const isInPlace = cause === RoomUpdateCause.Timeline || cause === RoomUpdateCause.ReadReceipt; - if (!isSplice && !isInPlace) { - throw new Error(`Unsupported update cause: ${cause}`); + try { + await this.updateLock.acquireAsync(); + + const isSplice = cause === RoomUpdateCause.NewRoom || cause === RoomUpdateCause.RoomRemoved; + const isInPlace = cause === RoomUpdateCause.Timeline || cause === RoomUpdateCause.ReadReceipt; + if (!isSplice && !isInPlace) { + throw new Error(`Unsupported update cause: ${cause}`); + } + + if (cause === RoomUpdateCause.NewRoom) { + this.cachedOrderedRooms.push(room); + } else if (cause === RoomUpdateCause.RoomRemoved) { + const idx = this.cachedOrderedRooms.indexOf(room); + if (idx >= 0) this.cachedOrderedRooms.splice(idx, 1); + } + + // TODO: Optimize this to avoid useless operations: https://github.com/vector-im/riot-web/issues/14035 + // For example, we can skip updates to alphabetic (sometimes) and manually ordered tags + this.cachedOrderedRooms = await sortRoomsWithAlgorithm(this.cachedOrderedRooms, this.tagId, this.sortingAlgorithm); + + return true; + } finally { + await this.updateLock.release(); } - - if (cause === RoomUpdateCause.NewRoom) { - this.cachedOrderedRooms.push(room); - } else if (cause === RoomUpdateCause.RoomRemoved) { - const idx = this.cachedOrderedRooms.indexOf(room); - if (idx >= 0) this.cachedOrderedRooms.splice(idx, 1); - } - - // TODO: Optimize this to avoid useless operations: https://github.com/vector-im/riot-web/issues/14035 - // For example, we can skip updates to alphabetic (sometimes) and manually ordered tags - this.cachedOrderedRooms = await sortRoomsWithAlgorithm(this.cachedOrderedRooms, this.tagId, this.sortingAlgorithm); - - return true; } } diff --git a/src/stores/room-list/algorithms/list-ordering/OrderingAlgorithm.ts b/src/stores/room-list/algorithms/list-ordering/OrderingAlgorithm.ts index f581e30630..4ab7650367 100644 --- a/src/stores/room-list/algorithms/list-ordering/OrderingAlgorithm.ts +++ b/src/stores/room-list/algorithms/list-ordering/OrderingAlgorithm.ts @@ -17,6 +17,7 @@ limitations under the License. import { Room } from "matrix-js-sdk/src/models/room"; import { RoomUpdateCause, TagID } from "../../models"; import { SortAlgorithm } from "../models"; +import AwaitLock from "await-lock"; /** * Represents a list ordering algorithm. Subclasses should populate the @@ -25,6 +26,7 @@ import { SortAlgorithm } from "../models"; export abstract class OrderingAlgorithm { protected cachedOrderedRooms: Room[]; protected sortingAlgorithm: SortAlgorithm; + protected readonly updateLock = new AwaitLock(); protected constructor(protected tagId: TagID, initialSortingAlgorithm: SortAlgorithm) { // noinspection JSIgnoredPromiseFromCall From 6a191ea3ee5645c750f8a773ee29335b839343b5 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 30 Jun 2020 14:23:00 -0600 Subject: [PATCH 206/294] Handle room invites as new rooms We wouldn't have seen them before, so might as well treat them as new instead of tag changes. --- src/stores/room-list/RoomListStore2.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index b4b643687d..497b8e5530 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -29,6 +29,7 @@ import { IFilterCondition } from "./filters/IFilterCondition"; import { TagWatcher } from "./TagWatcher"; import RoomViewStore from "../RoomViewStore"; import { Algorithm, LIST_UPDATED_EVENT } from "./algorithms/Algorithm"; +import { EffectiveMembership, getEffectiveMembership } from "./membership"; interface IState { tagsEnabled?: boolean; @@ -247,7 +248,9 @@ export class RoomListStore2 extends AsyncStore { } } else if (payload.action === 'MatrixActions.Room.myMembership') { const membershipPayload = (payload); // TODO: Type out the dispatcher types - if (membershipPayload.oldMembership !== "join" && membershipPayload.membership === "join") { + const oldMembership = getEffectiveMembership(membershipPayload.oldMembership); + const newMembership = getEffectiveMembership(membershipPayload.membership); + if (oldMembership !== EffectiveMembership.Join && newMembership === EffectiveMembership.Join) { // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 console.log(`[RoomListDebug] Handling new room ${membershipPayload.room.roomId}`); @@ -276,8 +279,15 @@ export class RoomListStore2 extends AsyncStore { return; } + if (oldMembership !== EffectiveMembership.Invite && newMembership === EffectiveMembership.Invite) { + // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 + console.log(`[RoomListDebug] Handling invite to ${membershipPayload.room.roomId}`); + await this.handleRoomUpdate(membershipPayload.room, RoomUpdateCause.NewRoom); + return; + } + // If it's not a join, it's transitioning into a different list (possibly historical) - if (membershipPayload.oldMembership !== membershipPayload.membership) { + if (oldMembership !== newMembership) { // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 console.log(`[RoomListDebug] Handling membership change in ${membershipPayload.room.roomId}`); await this.handleRoomUpdate(membershipPayload.room, RoomUpdateCause.PossibleTagChange); From da2fd35094c12eeb03099a93c2bb8c065dc79e10 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 30 Jun 2020 14:36:11 -0600 Subject: [PATCH 207/294] Add sanity check to ensure we don't accidentally proliferate rooms This small check just ensures that we aren't about to blindly accept that the calling code knows what it is doing. There are some unknown cases where NewRoom gets fired for rooms we already know about, so in those cases we just change it to a PossibleTagChange which is what the caller likely intended. Many of the edge cases are unknown, though this can happen for an invite being accepted (for example). It's easier to handle it here instead of tracking down every single possibility and fixing it higher up. --- src/stores/room-list/algorithms/Algorithm.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/stores/room-list/algorithms/Algorithm.ts b/src/stores/room-list/algorithms/Algorithm.ts index 4fbc576dfa..f4221dfa86 100644 --- a/src/stores/room-list/algorithms/Algorithm.ts +++ b/src/stores/room-list/algorithms/Algorithm.ts @@ -587,6 +587,14 @@ export class Algorithm extends EventEmitter { public async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise { if (!this.algorithms) throw new Error("Not ready: no algorithms to determine tags from"); + if (cause === RoomUpdateCause.NewRoom) { + const roomTags = this.roomIdsToTags[room.roomId]; + if (roomTags && roomTags.length > 0) { + console.warn(`${room.roomId} is reportedly new but is already known - assuming TagChange instead`); + cause = RoomUpdateCause.PossibleTagChange; + } + } + if (cause === RoomUpdateCause.PossibleTagChange) { // TODO: Be smarter and splice rather than regen the planet. https://github.com/vector-im/riot-web/issues/14035 // TODO: No-op if no change. https://github.com/vector-im/riot-web/issues/14035 From 2a12fd1f6e85d0d201aa9d5e3f439f463930c5bc Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 30 Jun 2020 21:58:54 +0100 Subject: [PATCH 208/294] Fix User context menu alignment to match Figma Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/UserMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 8c06a06852..ef461bf1b4 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -204,7 +204,7 @@ export default class UserMenu extends React.Component { return ( From 9de42513dc0a64727baf3422c6b11eabd003cf78 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 30 Jun 2020 15:04:47 -0600 Subject: [PATCH 209/294] Handle sticky rooms when regenerating lists `setKnownRooms` is called to regenerate the room list, and if we don't take the sticky room out of the equation we end up with the room being duplicated. So, to make this easy, we simply remove the sticky room and handle it after the fact. --- src/stores/room-list/algorithms/Algorithm.ts | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/stores/room-list/algorithms/Algorithm.ts b/src/stores/room-list/algorithms/Algorithm.ts index f4221dfa86..8215d2ef57 100644 --- a/src/stores/room-list/algorithms/Algorithm.ts +++ b/src/stores/room-list/algorithms/Algorithm.ts @@ -441,6 +441,13 @@ export class Algorithm extends EventEmitter { if (isNullOrUndefined(rooms)) throw new Error(`Array of rooms cannot be null`); if (!this.sortAlgorithms) throw new Error(`Cannot set known rooms without a tag sorting map`); + console.warn("Resetting known rooms, initiating regeneration"); + + // Before we go any further we need to clear (but remember) the sticky room to + // avoid accidentally duplicating it in the list. + const oldStickyRoom = this._stickyRoom; + await this.updateStickyRoom(null); + this.rooms = rooms; const newTags: ITagMap = {}; @@ -500,6 +507,21 @@ export class Algorithm extends EventEmitter { this.cachedRooms = newTags; this.updateTagsFromCache(); + this.recalculateFilteredRooms(); + + // Now that we've finished generation, we need to update the sticky room to what + // it was. It's entirely possible that it changed lists though, so if it did then + // we also have to update the position of it. + if (oldStickyRoom && oldStickyRoom.room) { + await this.updateStickyRoom(oldStickyRoom.room); + if (this._stickyRoom && this._stickyRoom.room) { // just in case the update doesn't go according to plan + if (this._stickyRoom.tag !== oldStickyRoom.tag) { + // We put the sticky room at the top of the list to treat it as an obvious tag change. + this._stickyRoom.position = 0; + this.recalculateStickyRoom(this._stickyRoom.tag); + } + } + } } private getTagsForRoom(room: Room): TagID[] { From cbd2a9cd359717556237e8876e9238950e253760 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 30 Jun 2020 22:11:12 +0100 Subject: [PATCH 210/294] Redo Iconized Context Menu styling to match Figma and simplify future a11y work by flattening the DOM Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/_common.scss | 115 +++++++++-------------- res/css/structures/_UserMenu.scss | 4 + src/components/structures/UserMenu.tsx | 78 ++++++--------- src/components/views/rooms/RoomTile2.tsx | 86 ++++++----------- 4 files changed, 107 insertions(+), 176 deletions(-) diff --git a/res/css/_common.scss b/res/css/_common.scss index 560bd894c6..0d057d96ab 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -588,27 +588,16 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { // A context menu that largely fits the | [icon] [label] | format. .mx_IconizedContextMenu { - // Put 20px of padding around the whole menu. We do this instead of a - // simple `padding: 20px` rule so the horizontal rules added by the - // optionLists is rendered correctly (full width). - > * { - padding-left: 20px; - padding-right: 20px; - - &:first-child { - padding-top: 20px; - } - - &:last-child { - padding-bottom: 16px; - } - } + min-width: 146px; .mx_IconizedContextMenu_optionList { + & > * { + padding-left: 20px; + padding-right: 20px; + } + // the notFirst class is for cases where the optionList might be under a header of sorts. &:nth-child(n + 2), .mx_IconizedContextMenu_optionList_notFirst { - margin-top: 12px; - // This is a bit of a hack when we could just use a simple border-top property, // however we have a (kinda) good reason for doing it this way: we need opacity. // To get the right color, we need an opacity modifier which means we have to work @@ -631,72 +620,54 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { } } - ul { - list-style: none; - margin: 0; - padding: 0; + // round the top corners of the top button for the hover effect to be bounded + &:first-child .mx_AccessibleButton:first-child { + border-radius: 4px 4px 0 0; // radius matches .mx_ContextualMenu + } - li { - margin: 0; - padding: 12px 0 0; + // round the bottom corners of the bottom button for the hover effect to be bounded + &:last-child .mx_AccessibleButton:last-child { + border-radius: 0 0 4px 4px; // radius matches .mx_ContextualMenu + } - .mx_AccessibleButton { - text-decoration: none; - color: $primary-fg-color; - font-size: $font-15px; - line-height: $font-24px; + .mx_AccessibleButton { + padding-top: 12px; + padding-bottom: 12px; + text-decoration: none; + color: $primary-fg-color; + font-size: $font-15px; + line-height: $font-24px; - // Create a flexbox to more easily define the list items - display: flex; - align-items: center; + // Create a flexbox to more easily define the list items + display: flex; + align-items: center; - img, .mx_IconizedContextMenu_icon { // icons - width: 16px; - min-width: 16px; - max-width: 16px; - } + &:hover { + background-color: $menu-selected-color; + } - span:last-child { // labels - padding-left: 14px; - width: 100%; - flex: 1; + img, .mx_IconizedContextMenu_icon { // icons + width: 16px; + min-width: 16px; + max-width: 16px; + } - // Ellipsize any text overflow - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - } - } + span:last-child { // labels + padding-left: 14px; + width: 100%; + flex: 1; + + // Ellipsize any text overflow + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; } } } &.mx_IconizedContextMenu_compact { - > * { - padding-left: 11px; - padding-right: 16px; - - &:first-child { - padding-top: 13px; - } - - &:last-child { - padding-bottom: 13px; - } - } - - .mx_IconizedContextMenu_optionList { - &:nth-child(n + 2), .mx_IconizedContextMenu_optionList_notFirst { - margin-top: 10px; - - li:first-child { - padding-top: 10px; - } - } - - li:first-child { - padding-top: 0; - } + .mx_IconizedContextMenu_optionList > * { + padding: 8px 16px 8px 11px; } } } diff --git a/res/css/structures/_UserMenu.scss b/res/css/structures/_UserMenu.scss index bbb1e1cc7b..c958b9eacd 100644 --- a/res/css/structures/_UserMenu.scss +++ b/res/css/structures/_UserMenu.scss @@ -86,6 +86,8 @@ limitations under the License. .mx_UserMenu_contextMenu_redRow { .mx_AccessibleButton { + padding-top: 16px; + padding-bottom: 16px; color: $warning-color !important; // !important to override styles from context menu } @@ -95,6 +97,8 @@ limitations under the License. } .mx_UserMenu_contextMenu_header { + padding: 20px; + // Create a flexbox to organize the header a bit easier display: flex; align-items: center; diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index ef461bf1b4..bd222c7f25 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -191,12 +191,10 @@ export default class UserMenu extends React.Component { let homeButton = null; if (this.hasHomePage) { homeButton = ( -
          • - - - {_t("Home")} - -
          • + + + {_t("Home")} + ); } @@ -232,49 +230,33 @@ 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")} - -
          • -
          + {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")} +
          -
          -
            -
          • - - - {_t("Sign out")} - -
          • -
          +
          + + + {_t("Sign out")} +
          diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index 52b3d444ac..f290edc5dd 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -66,7 +66,7 @@ interface IState { generalMenuDisplayed: boolean; } -export const contextMenuBelow = (elementRect) => { +const contextMenuBelow = (elementRect) => { const left = elementRect.left + window.pageXOffset - 6; let top = elementRect.bottom + window.pageYOffset + 21; const chevronFace = "none"; @@ -185,32 +185,22 @@ export default class RoomTile2 extends React.Component {
          -
            -
          • - - - {_t("All messages")} - -
          • -
          • - - - {_t("Default")} - -
          • -
          • - - - {_t("Mentions & Keywords")} - -
          • -
          • - - - {_t("None")} - -
          • -
          + + + {_t("All messages")} + + + + {_t("Default")} + + + + {_t("Mentions & Keywords")} + + + + {_t("None")} +
          @@ -252,36 +242,20 @@ export default class RoomTile2 extends React.Component {
          -
            -
          • - this.onTagRoom(e, DefaultTagID.Favourite)}> - - {_t("Favourite")} - -
          • -
          • - this.onTagRoom(e, DefaultTagID.LowPriority)}> - - {_t("Low Priority")} - -
          • -
          • - - - {_t("Settings")} - -
          • -
          + this.onTagRoom(e, DefaultTagID.Favourite)}> + + {_t("Favourite")} + + + + {_t("Settings")} +
          -
          -
            -
          • - - - {_t("Leave Room")} - -
          • -
          +
          + + + {_t("Leave Room")} +
          From 198958dcdd360baa20fe7459f5b0535edf7a3286 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 30 Jun 2020 22:32:59 +0100 Subject: [PATCH 211/294] Iterate to match figma Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/_common.scss | 2 +- res/css/views/rooms/_RoomTile2.scss | 13 ++++ src/components/structures/UserMenu.tsx | 12 ++-- src/components/views/rooms/RoomTile2.tsx | 79 +++++++++++++++++------- src/i18n/strings/en_EN.json | 3 +- 5 files changed, 80 insertions(+), 29 deletions(-) diff --git a/res/css/_common.scss b/res/css/_common.scss index 0d057d96ab..880b01a10e 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -652,7 +652,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { max-width: 16px; } - span:last-child { // labels + span.mx_IconizedContextMenu_label { // labels padding-left: 14px; width: 100%; flex: 1; diff --git a/res/css/views/rooms/_RoomTile2.scss b/res/css/views/rooms/_RoomTile2.scss index 78a7732882..8933c73045 100644 --- a/res/css/views/rooms/_RoomTile2.scss +++ b/res/css/views/rooms/_RoomTile2.scss @@ -166,6 +166,9 @@ limitations under the License. .mx_RoomTile2_iconBellCrossed::before { mask-image: url('$(res)/img/feather-customised/bell-crossed.svg'); } +.mx_RoomTile2_iconCheck::before { + mask-image: url('$(res)/img/feather-customised/check.svg'); +} .mx_RoomTile2_contextMenu { .mx_RoomTile2_contextMenu_redRow { @@ -178,6 +181,16 @@ limitations under the License. } } + .mx_RoomTile2_contextMenu_activeRow { + &.mx_AccessibleButton, .mx_AccessibleButton { + color: $accent-color !important; // !important to override styles from context menu + } + + .mx_IconizedContextMenu_icon::before { + background-color: $accent-color; + } + } + .mx_IconizedContextMenu_icon { position: relative; width: 16px; diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index bd222c7f25..4a45162902 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -233,29 +233,29 @@ export default class UserMenu extends React.Component { {homeButton} this.onSettingsOpen(e, USER_NOTIFICATIONS_TAB)}> - {_t("Notification settings")} + {_t("Notification settings")} this.onSettingsOpen(e, USER_SECURITY_TAB)}> - {_t("Security & privacy")} + {_t("Security & privacy")} this.onSettingsOpen(e, null)}> - {_t("All settings")} + {_t("All settings")} - {_t("Archived rooms")} + {_t("Archived rooms")} - {_t("Feedback")} + {_t("Feedback")}
          - {_t("Sign out")} + {_t("Sign out")}
          diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index f290edc5dd..4ed167d594 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -32,7 +32,7 @@ import NotificationBadge, { TagSpecificNotificationState } from "./NotificationBadge"; import { _t } from "../../../languageHandler"; -import { ContextMenu, ContextMenuButton } from "../../structures/ContextMenu"; +import { ContextMenu, ContextMenuButton, MenuItemRadio } from "../../structures/ContextMenu"; import { DefaultTagID, TagID } from "../../../stores/room-list/models"; import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore"; import RoomTileIcon from "./RoomTileIcon"; @@ -73,6 +73,34 @@ const contextMenuBelow = (elementRect) => { return {left, top, chevronFace}; }; +type State = ALL_MESSAGES_LOUD | ALL_MESSAGES | MENTIONS_ONLY | MUTE; + +interface INotifOptionProps { + active: boolean; + iconClassName: string; + label: string; + onClick(); +} + +const NotifOption: React.FC = ({active, onClick, iconClassName, label}) => { + const classes = classNames({ + mx_RoomTile2_contextMenu_activeRow: active, + }); + + let activeIcon; + if (active) { + activeIcon = ; + } + + return ( + + + { label } + { activeIcon } + + ); +}; + export default class RoomTile2 extends React.Component { private notificationsMenuButtonRef: React.RefObject = createRef(); private generalMenuButtonRef: React.RefObject = createRef(); @@ -178,6 +206,8 @@ export default class RoomTile2 extends React.Component { private renderNotificationsMenu(): React.ReactElement { if (this.props.isMinimized) return null; // no menu when minimized + const state = getRoomNotifsState(this.props.room.roomId); + let contextMenu = null; if (this.state.notificationsMenuDisplayed) { const elementRect = this.notificationsMenuButtonRef.current.getBoundingClientRect(); @@ -185,29 +215,36 @@ export default class RoomTile2 extends React.Component {
          - - - {_t("All messages")} - - - - {_t("Default")} - - - - {_t("Mentions & Keywords")} - - - - {_t("None")} - + + + +
          ); } - const state = getRoomNotifsState(this.props.room.roomId); const classes = classNames("mx_RoomTile2_notificationsButton", { // Show bell icon for the default case too. mx_RoomTile2_iconBell: state === ALL_MESSAGES_LOUD || state === ALL_MESSAGES, @@ -244,17 +281,17 @@ export default class RoomTile2 extends React.Component {
          this.onTagRoom(e, DefaultTagID.Favourite)}> - {_t("Favourite")} + {_t("Favourite")} - {_t("Settings")} + {_t("Settings")}
          - {_t("Leave Room")} + {_t("Leave Room")}
          diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index e056576cb1..b23264a297 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1218,11 +1218,11 @@ "%(count)s unread messages.|one": "1 unread message.", "Unread mentions.": "Unread mentions.", "Unread messages.": "Unread messages.", + "Use default": "Use default", "All messages": "All messages", "Mentions & Keywords": "Mentions & Keywords", "Notification options": "Notification options", "Favourite": "Favourite", - "Low Priority": "Low Priority", "Leave Room": "Leave Room", "Room options": "Room options", "Add a topic": "Add a topic", @@ -1903,6 +1903,7 @@ "Mentions only": "Mentions only", "Leave": "Leave", "Forget": "Forget", + "Low Priority": "Low Priority", "Direct Chat": "Direct Chat", "Clear status": "Clear status", "Update status": "Update status", From 508dea1c8976eb8c4e05ad5cdcd7f5a547b160f2 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 30 Jun 2020 22:53:30 +0100 Subject: [PATCH 212/294] Wire up Notifications context menu on room tile 2 Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/RoomTile2.tsx | 40 +++++++++++++++++++----- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index 4ed167d594..aad9b00860 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -37,6 +37,8 @@ import { DefaultTagID, TagID } from "../../../stores/room-list/models"; import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore"; import RoomTileIcon from "./RoomTileIcon"; import { getRoomNotifsState, ALL_MESSAGES, ALL_MESSAGES_LOUD, MENTIONS_ONLY, MUTE } from "../../../RoomNotifs"; +import { MatrixClientPeg } from "../../../MatrixClientPeg"; +import { setRoomNotifsState } from "../../../RoomNotifs"; // 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 @@ -73,13 +75,11 @@ const contextMenuBelow = (elementRect) => { return {left, top, chevronFace}; }; -type State = ALL_MESSAGES_LOUD | ALL_MESSAGES | MENTIONS_ONLY | MUTE; - interface INotifOptionProps { active: boolean; iconClassName: string; label: string; - onClick(); + onClick(ev: ButtonEvent); } const NotifOption: React.FC = ({active, onClick, iconClassName, label}) => { @@ -203,8 +203,32 @@ export default class RoomTile2 extends React.Component { this.setState({generalMenuDisplayed: false}); // hide the menu }; + private async saveNotifState(ev: ButtonEvent, newState: ALL_MESSAGES_LOUD | ALL_MESSAGES | MENTIONS_ONLY | MUTE) { + ev.preventDefault(); + ev.stopPropagation(); + if (MatrixClientPeg.get().isGuest()) return; + + try { + // TODO add local echo + await setRoomNotifsState(this.props.room.roomId, newState); + } catch (error) { + // TODO: some form of error notification to the user to inform them that their state change failed. + console.error(error); + } + + // Close the context menu + this.setState({ + notificationsMenuDisplayed: false, + }); + } + + private onClickAllNotifs = ev => this.saveNotifState(ev, ALL_MESSAGES); + private onClickAlertMe = ev => this.saveNotifState(ev, ALL_MESSAGES_LOUD); + private onClickMentions = ev => this.saveNotifState(ev, MENTIONS_ONLY); + private onClickMute = ev => this.saveNotifState(ev, MUTE); + private renderNotificationsMenu(): React.ReactElement { - if (this.props.isMinimized) return null; // no menu when minimized + if (this.props.isMinimized || MatrixClientPeg.get().isGuest()) return null; // no menu when minimized or guest const state = getRoomNotifsState(this.props.room.roomId); @@ -219,25 +243,25 @@ export default class RoomTile2 extends React.Component { label={_t("Use default")} active={state === ALL_MESSAGES} iconClassName="mx_RoomTile2_iconBell" - onClick={this._onClickAllNotifs} + onClick={this.onClickAllNotifs} />
          From dcd51b5be3369cf87a89d937c5eddb94669b2d58 Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Tue, 30 Jun 2020 23:24:46 +0100 Subject: [PATCH 213/294] Implement breadcrumb notifications and scrolling --- res/css/_components.scss | 1 + res/css/structures/_LeftPanel2.scss | 3 +- .../views/avatars/_DecoratedRoomAvatar.scss | 33 ++++++++++ res/css/views/rooms/_RoomTile2.scss | 17 +---- src/components/structures/LeftPanel2.tsx | 2 +- .../views/avatars/DecoratedRoomAvatar.tsx | 65 +++++++++++++++++++ .../views/rooms/RoomBreadcrumbs2.tsx | 14 +++- src/components/views/rooms/RoomTile2.tsx | 24 ++++--- src/stores/room-list/RoomListStore2.ts | 18 ++++- src/stores/room-list/algorithms/Algorithm.ts | 11 ++++ 10 files changed, 157 insertions(+), 31 deletions(-) create mode 100644 res/css/views/avatars/_DecoratedRoomAvatar.scss create mode 100644 src/components/views/avatars/DecoratedRoomAvatar.tsx diff --git a/res/css/_components.scss b/res/css/_components.scss index afc40ca0d6..8288cf34f6 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -49,6 +49,7 @@ @import "./views/auth/_ServerTypeSelector.scss"; @import "./views/auth/_Welcome.scss"; @import "./views/avatars/_BaseAvatar.scss"; +@import "./views/avatars/_DecoratedRoomAvatar.scss"; @import "./views/avatars/_MemberStatusMessageAvatar.scss"; @import "./views/context_menus/_MessageContextMenu.scss"; @import "./views/context_menus/_RoomTileContextMenu.scss"; diff --git a/res/css/structures/_LeftPanel2.scss b/res/css/structures/_LeftPanel2.scss index 40babaa9ca..bdaada0d15 100644 --- a/res/css/structures/_LeftPanel2.scss +++ b/res/css/structures/_LeftPanel2.scss @@ -70,7 +70,8 @@ $tagPanelWidth: 70px; // only applies in this file, used for calculations .mx_LeftPanel2_breadcrumbsContainer { width: 100%; - overflow: hidden; + overflow-y: hidden; + overflow-x: scroll; margin-top: 8px; } } diff --git a/res/css/views/avatars/_DecoratedRoomAvatar.scss b/res/css/views/avatars/_DecoratedRoomAvatar.scss new file mode 100644 index 0000000000..984fa0ce9a --- /dev/null +++ b/res/css/views/avatars/_DecoratedRoomAvatar.scss @@ -0,0 +1,33 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_DecoratedRoomAvatar { + position: relative; + + .mx_RoomTileIcon { + position: absolute; + bottom: 0; + right: 0; + } + + .mx_NotificationBadge { + position: absolute; + top: 0; + right: 0; + height: 18px; + width: 18px; + } +} diff --git a/res/css/views/rooms/_RoomTile2.scss b/res/css/views/rooms/_RoomTile2.scss index 2845068de3..e4e6a3eac1 100644 --- a/res/css/views/rooms/_RoomTile2.scss +++ b/res/css/views/rooms/_RoomTile2.scss @@ -30,15 +30,8 @@ limitations under the License. border-radius: 32px; } - .mx_RoomTile2_avatarContainer { + .mx_DecoratedRoomAvatar { margin-right: 8px; - position: relative; - - .mx_RoomTileIcon { - position: absolute; - bottom: 0; - right: 0; - } } .mx_RoomTile2_nameContainer { @@ -145,16 +138,10 @@ limitations under the License. align-items: center; position: relative; - .mx_RoomTile2_avatarContainer { + .mx_DecoratedRoomAvatar { margin-right: 0; } - .mx_RoomTile2_badgeContainer { - position: absolute; - top: 0; - right: 0; - height: 18px; - } } } diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index 0f614435e5..b4ec897561 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -151,7 +151,7 @@ export default class LeftPanel2 extends React.Component { let breadcrumbs; if (this.state.showBreadcrumbs) { breadcrumbs = ( -
          +
          {this.props.isMinimized ? null : }
          ); diff --git a/src/components/views/avatars/DecoratedRoomAvatar.tsx b/src/components/views/avatars/DecoratedRoomAvatar.tsx new file mode 100644 index 0000000000..af1cdc779c --- /dev/null +++ b/src/components/views/avatars/DecoratedRoomAvatar.tsx @@ -0,0 +1,65 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import { Room } from "matrix-js-sdk/src/models/room"; + +import { TagID } from '../../../stores/room-list/models'; +import RoomAvatar from "./RoomAvatar"; +import RoomTileIcon from "../rooms/RoomTileIcon"; +import NotificationBadge, { INotificationState, TagSpecificNotificationState } from '../rooms/NotificationBadge'; + +interface IProps { + room: Room; + avatarSize: number; + tag: TagID; + displayBadge?: boolean; + forceCount?: boolean; +} + +interface IState { + notificationState?: INotificationState; +} + +export default class DecoratedRoomAvatar extends React.PureComponent { + + constructor(props: IProps) { + super(props); + + this.state = { + notificationState: new TagSpecificNotificationState(this.props.room, this.props.tag), + } + } + + public render(): React.ReactNode { + console.log({tag: this.props.tag}) + + let badge: React.ReactNode; + if (this.props.displayBadge) { + badge = ; + } + + return
          + + + {badge} +
          + } +} \ No newline at end of file diff --git a/src/components/views/rooms/RoomBreadcrumbs2.tsx b/src/components/views/rooms/RoomBreadcrumbs2.tsx index bd12ced6ee..2f2b815002 100644 --- a/src/components/views/rooms/RoomBreadcrumbs2.tsx +++ b/src/components/views/rooms/RoomBreadcrumbs2.tsx @@ -17,13 +17,15 @@ limitations under the License. import React from "react"; import { BreadcrumbsStore } from "../../../stores/BreadcrumbsStore"; import AccessibleButton from "../elements/AccessibleButton"; -import RoomAvatar from "../avatars/RoomAvatar"; +import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar"; import { _t } from "../../../languageHandler"; import { Room } from "matrix-js-sdk/src/models/room"; import defaultDispatcher from "../../../dispatcher/dispatcher"; import Analytics from "../../../Analytics"; import { UPDATE_EVENT } from "../../../stores/AsyncStore"; import { CSSTransition } from "react-transition-group"; +import RoomListStore from "../../../stores/room-list/RoomListStore2"; +import { DefaultTagID } from "../../../stores/room-list/models"; // 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 @@ -93,6 +95,8 @@ export default class RoomBreadcrumbs2 extends React.PureComponent { + const roomTags = RoomListStore.instance.getTagsForRoom(r) + const roomTag = roomTags.includes(DefaultTagID.DM) ? DefaultTagID.DM : roomTags[0]; return ( this.viewRoom(r, i)} aria-label={_t("Room %(name)s", {name: r.name})} > - + ); }); diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index 1284728855..f8c46ee85a 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -22,7 +22,6 @@ import { Room } from "matrix-js-sdk/src/models/room"; import classNames from "classnames"; import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex"; import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton"; -import RoomAvatar from "../../views/avatars/RoomAvatar"; import dis from '../../../dispatcher/dispatcher'; import { Key } from "../../../Keyboard"; import ActiveRoomObserver from "../../../ActiveRoomObserver"; @@ -35,7 +34,7 @@ import { _t } from "../../../languageHandler"; import { ContextMenu, ContextMenuButton } from "../../structures/ContextMenu"; import { DefaultTagID, TagID } from "../../../stores/room-list/models"; import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore"; -import RoomTileIcon from "./RoomTileIcon"; +import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar"; // 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 @@ -233,13 +232,22 @@ export default class RoomTile2 extends React.Component { 'mx_RoomTile2_minimized': this.props.isMinimized, }); - const badge = ( - + + let badge: React.ReactNode; + if (!this.props.isMinimized) { + badge = - ); + } // TODO: the original RoomTile uses state for the room name. Do we need to? let name = this.props.room.name; @@ -277,7 +285,6 @@ export default class RoomTile2 extends React.Component { ); if (this.props.isMinimized) nameContainer = null; - const avatarSize = 32; return ( @@ -292,10 +299,7 @@ export default class RoomTile2 extends React.Component { onClick={this.onTileClick} role="treeitem" > -
          - - -
          + {roomAvatar} {nameContainer}
          {badge} diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index 497b8e5530..b4d96becc4 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -17,7 +17,7 @@ limitations under the License. import { MatrixClient } from "matrix-js-sdk/src/client"; import SettingsStore from "../../settings/SettingsStore"; -import { OrderedDefaultTagIDs, RoomUpdateCause, TagID } from "./models"; +import { DefaultTagID, OrderedDefaultTagIDs, RoomUpdateCause, TagID } from "./models"; import TagOrderStore from "../TagOrderStore"; import { AsyncStore } from "../AsyncStore"; import { Room } from "matrix-js-sdk/src/models/room"; @@ -187,7 +187,8 @@ export class RoomListStore2 extends AsyncStore { const room = this.matrixClient.getRoom(roomId); const tryUpdate = async (updatedRoom: Room) => { // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 - console.log(`[RoomListDebug] Live timeline event ${eventPayload.event.getId()} in ${updatedRoom.roomId}`); + console.log(`[RoomListDebug] Live timeline event ${eventPayload.event.getId()}` + + ` in ${updatedRoom.roomId}`); if (eventPayload.event.getType() === 'm.room.tombstone' && eventPayload.event.getStateKey() === '') { // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 console.log(`[RoomListDebug] Got tombstone event - trying to remove now-dead room`); @@ -422,6 +423,19 @@ export class RoomListStore2 extends AsyncStore { } } } + + /** + * Gets the tags for a room identified by the store. The returned set + * should never be empty, and will contain DefaultTagID.Untagged if + * the store is not aware of any tags. + * @param room The room to get the tags for. + * @returns The tags for the room. + */ + public getTagsForRoom(room: Room): TagID[] { + const algorithmTags = this.algorithm.getTagsForRoom(room); + if (!algorithmTags) return [DefaultTagID.Untagged]; + return algorithmTags; + } } export default class RoomListStore { diff --git a/src/stores/room-list/algorithms/Algorithm.ts b/src/stores/room-list/algorithms/Algorithm.ts index 8215d2ef57..d4615356da 100644 --- a/src/stores/room-list/algorithms/Algorithm.ts +++ b/src/stores/room-list/algorithms/Algorithm.ts @@ -670,4 +670,15 @@ export class Algorithm extends EventEmitter { return true; } + + /** + * Returns the tags for a given room as known by the algorithm. May be null or + * empty. + * @param room The room to get known tags for. + * @returns The known tags for the room. + */ + public getTagsForRoom(room: Room): TagID[] { + if (!room) throw new Error("A room is required"); + return this.roomIdsToTags[room.roomId]; + } } From 0904ae8c7a27c6417f355a68010399754f48e350 Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Tue, 30 Jun 2020 23:35:59 +0100 Subject: [PATCH 214/294] Bug fixes --- .../views/avatars/DecoratedRoomAvatar.tsx | 2 +- src/stores/room-list/algorithms/Algorithm.ts | 13 +------------ 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/components/views/avatars/DecoratedRoomAvatar.tsx b/src/components/views/avatars/DecoratedRoomAvatar.tsx index af1cdc779c..5fb3287980 100644 --- a/src/components/views/avatars/DecoratedRoomAvatar.tsx +++ b/src/components/views/avatars/DecoratedRoomAvatar.tsx @@ -58,7 +58,7 @@ export default class DecoratedRoomAvatar extends React.PureComponent - + {badge}
          } diff --git a/src/stores/room-list/algorithms/Algorithm.ts b/src/stores/room-list/algorithms/Algorithm.ts index d4615356da..36abf86975 100644 --- a/src/stores/room-list/algorithms/Algorithm.ts +++ b/src/stores/room-list/algorithms/Algorithm.ts @@ -524,7 +524,7 @@ export class Algorithm extends EventEmitter { } } - private getTagsForRoom(room: Room): TagID[] { + public getTagsForRoom(room: Room): TagID[] { // XXX: This duplicates a lot of logic from setKnownRooms above, but has a slightly // different use case and therefore different performance curve @@ -670,15 +670,4 @@ export class Algorithm extends EventEmitter { return true; } - - /** - * Returns the tags for a given room as known by the algorithm. May be null or - * empty. - * @param room The room to get known tags for. - * @returns The known tags for the room. - */ - public getTagsForRoom(room: Room): TagID[] { - if (!room) throw new Error("A room is required"); - return this.roomIdsToTags[room.roomId]; - } } From 2379ec577cbfa6e3e83ab5f06b339a5ef36ed06f Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Tue, 30 Jun 2020 23:39:25 +0100 Subject: [PATCH 215/294] Lint semicolons --- src/components/views/avatars/DecoratedRoomAvatar.tsx | 6 ++---- src/components/views/rooms/RoomBreadcrumbs2.tsx | 2 +- src/components/views/rooms/RoomTile2.tsx | 6 +++--- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/components/views/avatars/DecoratedRoomAvatar.tsx b/src/components/views/avatars/DecoratedRoomAvatar.tsx index 5fb3287980..1156c80313 100644 --- a/src/components/views/avatars/DecoratedRoomAvatar.tsx +++ b/src/components/views/avatars/DecoratedRoomAvatar.tsx @@ -41,12 +41,10 @@ export default class DecoratedRoomAvatar extends React.PureComponent {badge} -
          +
          ; } } \ No newline at end of file diff --git a/src/components/views/rooms/RoomBreadcrumbs2.tsx b/src/components/views/rooms/RoomBreadcrumbs2.tsx index 2f2b815002..c0417fc592 100644 --- a/src/components/views/rooms/RoomBreadcrumbs2.tsx +++ b/src/components/views/rooms/RoomBreadcrumbs2.tsx @@ -95,7 +95,7 @@ export default class RoomBreadcrumbs2 extends React.PureComponent { - const roomTags = RoomListStore.instance.getTagsForRoom(r) + const roomTags = RoomListStore.instance.getTagsForRoom(r); const roomTag = roomTags.includes(DefaultTagID.DM) ? DefaultTagID.DM : roomTags[0]; return ( { avatarSize={avatarSize} tag={this.props.tag} displayBadge={this.props.isMinimized} - /> + />; - let badge: React.ReactNode; + let badge: React.ReactNode; if (!this.props.isMinimized) { badge = + />; } // TODO: the original RoomTile uses state for the room name. Do we need to? From b1e0b35758cd28dd7e967c48dc181b5c7af12aeb Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Tue, 30 Jun 2020 23:40:24 +0100 Subject: [PATCH 216/294] Lint style --- res/css/views/rooms/_RoomTile2.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/res/css/views/rooms/_RoomTile2.scss b/res/css/views/rooms/_RoomTile2.scss index e4e6a3eac1..0a425d890f 100644 --- a/res/css/views/rooms/_RoomTile2.scss +++ b/res/css/views/rooms/_RoomTile2.scss @@ -141,7 +141,6 @@ limitations under the License. .mx_DecoratedRoomAvatar { margin-right: 0; } - } } From d2fb30a2116313c70943eb0e7bc37e644c2a7e7f Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Tue, 30 Jun 2020 23:52:13 +0100 Subject: [PATCH 217/294] Hide scrollbar without pixel jumping --- src/components/structures/LeftPanel2.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index 0f614435e5..d34986f981 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -15,6 +15,7 @@ limitations under the License. */ import * as React from "react"; +import classnames from 'classnames'; import { createRef } from "react"; import TagPanel from "./TagPanel"; import classNames from "classnames"; @@ -205,6 +206,11 @@ export default class LeftPanel2 extends React.Component { "mx_LeftPanel2_minimized": this.props.isMinimized, }); + const className = classnames( + "mx_LeftPanel2_actualRoomListContainer", + "mx_AutoHideScrollbar", + ); + return (
          {tagPanel} @@ -212,7 +218,7 @@ export default class LeftPanel2 extends React.Component { {this.renderHeader()} {this.renderSearchExplore()}
          {roomList}
          From aab372c6484fc59d7ea30c55aadf4df8e7aee1aa Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Wed, 1 Jul 2020 01:50:31 +0100 Subject: [PATCH 218/294] Add tooltips --- res/css/views/elements/_Tooltip.scss | 2 +- res/css/views/rooms/_RoomSublist2.scss | 4 ++ src/components/structures/UserMenu.tsx | 5 ++- .../views/elements/AccessibleButton.tsx | 2 +- ...pButton.js => AccessibleTooltipButton.tsx} | 38 +++++++++++-------- src/components/views/rooms/RoomSublist2.tsx | 6 ++- 6 files changed, 37 insertions(+), 20 deletions(-) rename src/components/views/elements/{AccessibleTooltipButton.js => AccessibleTooltipButton.tsx} (70%) diff --git a/res/css/views/elements/_Tooltip.scss b/res/css/views/elements/_Tooltip.scss index 73ac9b3558..d67928bf83 100644 --- a/res/css/views/elements/_Tooltip.scss +++ b/res/css/views/elements/_Tooltip.scss @@ -55,7 +55,7 @@ limitations under the License. border-radius: 4px; box-shadow: 4px 4px 12px 0 $menu-box-shadow-color; background-color: $menu-bg-color; - z-index: 4000; // Higher than dialogs so tooltips can be used in dialogs + z-index: 6000; // Higher than context menu so tooltips can be used everywhere padding: 10px; pointer-events: none; line-height: $font-14px; diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss index ffb96cf600..e86bc83cc8 100644 --- a/res/css/views/rooms/_RoomSublist2.scss +++ b/res/css/views/rooms/_RoomSublist2.scss @@ -389,3 +389,7 @@ limitations under the License. margin-top: 8px; } } + +.mx_RoomSublist2_addRoomTooltip { + margin-top: -3px; +} diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 8c06a06852..df8c777aeb 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -37,6 +37,7 @@ import { OwnProfileStore } from "../../stores/OwnProfileStore"; import { UPDATE_EVENT } from "../../stores/AsyncStore"; import BaseAvatar from '../views/avatars/BaseAvatar'; import classNames from "classnames"; +import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton"; interface IProps { isMinimized: boolean; @@ -218,7 +219,7 @@ export default class UserMenu extends React.Component { {MatrixClientPeg.get().getUserId()}
          -
          { alt={_t("Switch theme")} width={16} /> -
          +
          {hostingLink}
          diff --git a/src/components/views/elements/AccessibleButton.tsx b/src/components/views/elements/AccessibleButton.tsx index 01a27d9522..040147bb16 100644 --- a/src/components/views/elements/AccessibleButton.tsx +++ b/src/components/views/elements/AccessibleButton.tsx @@ -27,7 +27,7 @@ export type ButtonEvent = React.MouseEvent | React.KeyboardEvent { +export interface IProps extends React.InputHTMLAttributes { inputRef?: React.Ref; element?: string; // The kind of button, similar to how Bootstrap works. diff --git a/src/components/views/elements/AccessibleTooltipButton.js b/src/components/views/elements/AccessibleTooltipButton.tsx similarity index 70% rename from src/components/views/elements/AccessibleTooltipButton.js rename to src/components/views/elements/AccessibleTooltipButton.tsx index 6c84c6ab7e..1c0e18c399 100644 --- a/src/components/views/elements/AccessibleTooltipButton.js +++ b/src/components/views/elements/AccessibleTooltipButton.tsx @@ -16,21 +16,28 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; +import classnames from 'classnames'; import AccessibleButton from "./AccessibleButton"; -import * as sdk from "../../../index"; +import {IProps} from "./AccessibleButton"; +import Tooltip from './Tooltip'; -export default class AccessibleTooltipButton extends React.PureComponent { - static propTypes = { - ...AccessibleButton.propTypes, - // The tooltip to render on hover - title: PropTypes.string.isRequired, - }; +interface ITooltipProps extends IProps { + title: string; + tooltipClassName?: string; +} - state = { - hover: false, - }; +interface IState { + hover: boolean; +} + +export default class AccessibleTooltipButton extends React.PureComponent { + constructor(props: ITooltipProps) { + super(props) + this.state = { + hover: false, + }; + } onMouseOver = () => { this.setState({ @@ -45,14 +52,15 @@ export default class AccessibleTooltipButton extends React.PureComponent { }; render() { - const Tooltip = sdk.getComponent("elements.Tooltip"); - const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); - const {title, children, ...props} = this.props; + const tooltipClassName = classnames( + "mx_AccessibleTooltipButton_tooltip", + this.props.tooltipClassName, + ); const tip = this.state.hover ? :
          ; return ( diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index 58ebf54bf7..a6b2e72b6a 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -33,6 +33,8 @@ import StyledRadioButton from "../elements/StyledRadioButton"; import RoomListStore from "../../../stores/room-list/RoomListStore2"; import { ListAlgorithm, SortAlgorithm } from "../../../stores/room-list/algorithms/models"; import { TagID } from "../../../stores/room-list/models"; +import Tooltip from "../elements/Tooltip"; +import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; // TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14231 // TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231 @@ -277,11 +279,13 @@ export default class RoomSublist2 extends React.Component { let addRoomButton = null; if (!!this.props.onAddRoom) { addRoomButton = ( - ); } From f935303eeb65f96d0822a7ac9ed3d316c6ee16f6 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 30 Jun 2020 18:51:59 -0600 Subject: [PATCH 219/294] Change default number of rooms visible to 10 Fixes https://github.com/vector-im/riot-web/issues/14266 --- src/stores/room-list/ListLayout.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/stores/room-list/ListLayout.ts b/src/stores/room-list/ListLayout.ts index 56f94ccd9a..2cc8eda510 100644 --- a/src/stores/room-list/ListLayout.ts +++ b/src/stores/room-list/ListLayout.ts @@ -85,10 +85,8 @@ export class ListLayout { } public get defaultVisibleTiles(): number { - // TODO: Remove dogfood flag: https://github.com/vector-im/riot-web/issues/14231 - // TODO: Resolve dogfooding: https://github.com/vector-im/riot-web/issues/14137 - const val = Number(localStorage.getItem("mx_dogfood_rl_defTiles") || 4); - return val + RESIZER_BOX_FACTOR; + // 10 is what "feels right", and mostly subject to design's opinion. + return 10 + RESIZER_BOX_FACTOR; } public calculateTilesToPixelsMin(maxTiles: number, n: number, possiblePadding: number): number { From 8cfe12b817995be90ad68083a49a5263fc6fe0fd Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 30 Jun 2020 18:52:13 -0600 Subject: [PATCH 220/294] Add a layout reset function For https://github.com/vector-im/riot-web/issues/14265 Intended to be accessed via `mx_RoomListStore2.resetLayout()` --- src/stores/room-list/ListLayout.ts | 4 ++++ src/stores/room-list/RoomListStore2.ts | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/stores/room-list/ListLayout.ts b/src/stores/room-list/ListLayout.ts index 2cc8eda510..d8564bf947 100644 --- a/src/stores/room-list/ListLayout.ts +++ b/src/stores/room-list/ListLayout.ts @@ -120,6 +120,10 @@ export class ListLayout { return px / this.tileHeight; } + public reset() { + localStorage.removeItem(this.key); + } + private save() { localStorage.setItem(this.key, JSON.stringify(this.serialize())); } diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index 497b8e5530..ac2324295e 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -30,6 +30,7 @@ import { TagWatcher } from "./TagWatcher"; import RoomViewStore from "../RoomViewStore"; import { Algorithm, LIST_UPDATED_EVENT } from "./algorithms/Algorithm"; import { EffectiveMembership, getEffectiveMembership } from "./membership"; +import { ListLayout } from "./ListLayout"; interface IState { tagsEnabled?: boolean; @@ -401,6 +402,15 @@ export class RoomListStore2 extends AsyncStore { this.emit(LISTS_UPDATE_EVENT, this); } + // Note: this primarily exists for debugging, and isn't really intended to be used by anything. + public async resetLayouts() { + console.warn("Resetting layouts for room list"); + for (const tagId of Object.keys(this.orderedLists)) { + new ListLayout(tagId).reset(); + } + await this.regenerateAllLists(); + } + public addFilter(filter: IFilterCondition): void { // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 console.log("Adding filter condition:", filter); From 7674030c6eb32d7d9ede6f92e64be697f4b37bb1 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 30 Jun 2020 19:14:36 -0600 Subject: [PATCH 221/294] Show 'show more' when there are less tiles than the default For example, if you only have 3/10 rooms required for the default then resize smaller, we should have a 'show more' button. This works by changing the rendering to be slightly more efficient and only looping over what is seen (renderVisibleTiles(), using this.numTiles in place of tiles.length) and using a new setVisibleTilesWithin() function on the layout. Previously resizing the 3/10 case would be setting visibleTiles to ~8 instead of ~1 like it should (because the getter returns a default). --- src/components/views/rooms/RoomSublist2.tsx | 39 ++++++++++++--------- src/stores/room-list/ListLayout.ts | 8 +++++ 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index 58ebf54bf7..6682527254 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -91,6 +91,12 @@ export default class RoomSublist2 extends React.Component { return (this.props.rooms || []).length; } + private get numVisibleTiles(): number { + if (!this.props.layout) return 0; + const nVisible = Math.floor(this.props.layout.visibleTiles); + return Math.min(nVisible, this.numTiles); + } + public componentDidUpdate() { this.state.notificationState.setRooms(this.props.rooms); } @@ -107,7 +113,7 @@ export default class RoomSublist2 extends React.Component { private onResize = (e: React.MouseEvent, data: ResizeCallbackData) => { const direction = e.movementY < 0 ? -1 : +1; const tileDiff = this.props.layout.pixelsToTiles(Math.abs(e.movementY)) * direction; - this.props.layout.visibleTiles += tileDiff; + this.props.layout.setVisibleTilesWithin(tileDiff, this.numTiles); this.forceUpdate(); // because the layout doesn't trigger a re-render }; @@ -173,13 +179,17 @@ export default class RoomSublist2 extends React.Component { } }; - private renderTiles(): React.ReactElement[] { - if (this.props.layout && this.props.layout.isCollapsed) return []; // don't waste time on rendering + private renderVisibleTiles(): React.ReactElement[] { + if (this.props.layout && this.props.layout.isCollapsed) { + // don't waste time on rendering + return []; + } const tiles: React.ReactElement[] = []; if (this.props.rooms) { - for (const room of this.props.rooms) { + const visibleRooms = this.props.rooms.slice(0, this.numVisibleTiles); + for (const room of visibleRooms) { tiles.push( { public render(): React.ReactElement { // TODO: Error boundary: https://github.com/vector-im/riot-web/issues/14185 - const tiles = this.renderTiles(); + const visibleTiles = this.renderVisibleTiles(); const classes = classNames({ 'mx_RoomSublist2': true, @@ -347,13 +357,10 @@ export default class RoomSublist2 extends React.Component { }); let content = null; - if (tiles.length > 0) { + if (visibleTiles.length > 0) { const layout = this.props.layout; // to shorten calls - const nVisible = Math.floor(layout.visibleTiles); - const visibleTiles = tiles.slice(0, nVisible); - - const maxTilesFactored = layout.tilesWithResizerBoxFactor(tiles.length); + const maxTilesFactored = layout.tilesWithResizerBoxFactor(this.numTiles); const showMoreBtnClasses = classNames({ 'mx_RoomSublist2_showNButton': true, 'mx_RoomSublist2_isCutting': this.state.isResizing && layout.visibleTiles < maxTilesFactored, @@ -363,9 +370,9 @@ export default class RoomSublist2 extends React.Component { // floats above the resize handle, if we have one present. If the user has all // tiles visible, it becomes 'show less'. let showNButton = null; - if (tiles.length > nVisible) { + if (this.numTiles > this.numVisibleTiles) { // we have a cutoff condition - add the button to show all - const numMissing = tiles.length - visibleTiles.length; + const numMissing = this.numTiles - visibleTiles.length; let showMoreText = ( {_t("Show %(count)s more", {count: numMissing})} @@ -380,7 +387,7 @@ export default class RoomSublist2 extends React.Component { {showMoreText}
          ); - } else if (tiles.length <= nVisible && tiles.length > this.props.layout.defaultVisibleTiles) { + } else if (this.numTiles <= this.numVisibleTiles && this.numTiles > this.props.layout.defaultVisibleTiles) { // we have all tiles visible - add a button to show less let showLessText = ( @@ -400,7 +407,7 @@ export default class RoomSublist2 extends React.Component { // Figure out if we need a handle let handles = ['s']; - if (layout.visibleTiles >= tiles.length && tiles.length <= layout.minVisibleTiles) { + if (layout.visibleTiles >= this.numTiles && this.numTiles <= layout.minVisibleTiles) { handles = []; // no handles, we're at a minimum } @@ -419,9 +426,9 @@ export default class RoomSublist2 extends React.Component { if (showNButton) padding += SHOW_N_BUTTON_HEIGHT; padding += RESIZE_HANDLE_HEIGHT; // always append the handle height - const relativeTiles = layout.tilesWithPadding(tiles.length, padding); + const relativeTiles = layout.tilesWithPadding(this.numTiles, padding); const minTilesPx = layout.calculateTilesToPixelsMin(relativeTiles, layout.minVisibleTiles, padding); - const maxTilesPx = layout.tilesToPixelsWithPadding(tiles.length, padding); + const maxTilesPx = layout.tilesToPixelsWithPadding(this.numTiles, padding); const tilesWithoutPadding = Math.min(relativeTiles, layout.visibleTiles); const tilesPx = layout.calculateTilesToPixelsMin(relativeTiles, tilesWithoutPadding, padding); diff --git a/src/stores/room-list/ListLayout.ts b/src/stores/room-list/ListLayout.ts index d8564bf947..528276e801 100644 --- a/src/stores/room-list/ListLayout.ts +++ b/src/stores/room-list/ListLayout.ts @@ -89,6 +89,14 @@ export class ListLayout { return 10 + RESIZER_BOX_FACTOR; } + public setVisibleTilesWithin(diff: number, maxPossible: number) { + if (this.visibleTiles > maxPossible) { + this.visibleTiles = maxPossible + diff; + } else { + this.visibleTiles += diff; + } + } + public calculateTilesToPixelsMin(maxTiles: number, n: number, possiblePadding: number): number { // Only apply the padding if we're about to use maxTiles as we need to // plan for the padding. If we're using n, the padding is already accounted From 8cfbfd4221f962f6ae36a154b9a5210b43c2e31e Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 30 Jun 2020 19:20:11 -0600 Subject: [PATCH 222/294] Increase RESIZER_BOX_FACTOR to account for overlap from handle Fixes https://github.com/vector-im/riot-web/issues/14136 The resizer handle wasn't being considered in this. 78% is both verified through mathematics and playing with it manually. --- src/stores/room-list/ListLayout.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/stores/room-list/ListLayout.ts b/src/stores/room-list/ListLayout.ts index 528276e801..efb0c4bdfb 100644 --- a/src/stores/room-list/ListLayout.ts +++ b/src/stores/room-list/ListLayout.ts @@ -18,9 +18,9 @@ import { TagID } from "./models"; const TILE_HEIGHT_PX = 44; -// the .65 comes from the CSS where the show more button is -// mathematically 65% of a tile when floating. -const RESIZER_BOX_FACTOR = 0.65; +// this comes from the CSS where the show more button is +// mathematically this percent of a tile when floating. +const RESIZER_BOX_FACTOR = 0.78; interface ISerializedListLayout { numTiles: number; From d402808101f77d0319634efe67a7354c0da62308 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 30 Jun 2020 19:31:59 -0600 Subject: [PATCH 223/294] Disable use of account-level ordering options in new room list Fixes https://github.com/vector-im/riot-web/issues/14069 We can't drop them completely for compatibility with the old room list. --- .../tabs/user/PreferencesUserSettingsTab.js | 16 +++++++++++++++- src/settings/Settings.js | 2 ++ src/stores/room-list/RoomListStore2.ts | 9 ++------- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index bdb2a9ffc4..592331f897 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -23,6 +23,7 @@ import SettingsStore from "../../../../../settings/SettingsStore"; import Field from "../../../elements/Field"; import * as sdk from "../../../../.."; import PlatformPeg from "../../../../../PlatformPeg"; +import {RoomListStoreTempProxy} from "../../../../../stores/room-list/RoomListStoreTempProxy"; export default class PreferencesUserSettingsTab extends React.Component { static ROOM_LIST_SETTINGS = [ @@ -31,6 +32,19 @@ export default class PreferencesUserSettingsTab extends React.Component { 'breadcrumbs', ]; + // TODO: Remove temp structures: https://github.com/vector-im/riot-web/issues/14231 + static ROOM_LIST_2_SETTINGS = [ + 'breadcrumbs', + ]; + + // TODO: Remove temp structures: https://github.com/vector-im/riot-web/issues/14231 + static ELIGIBLE_ROOM_LIST_SETTINGS = () => { + if (RoomListStoreTempProxy.isUsingNewStore()) { + return PreferencesUserSettingsTab.ROOM_LIST_2_SETTINGS; + } + return PreferencesUserSettingsTab.ROOM_LIST_SETTINGS; + }; + static COMPOSER_SETTINGS = [ 'MessageComposerInput.autoReplaceEmoji', 'MessageComposerInput.suggestEmoji', @@ -175,7 +189,7 @@ export default class PreferencesUserSettingsTab extends React.Component {
          {_t("Room list")} - {this._renderGroup(PreferencesUserSettingsTab.ROOM_LIST_SETTINGS)} + {this._renderGroup(PreferencesUserSettingsTab.ELIGIBLE_ROOM_LIST_SETTINGS())}
          diff --git a/src/settings/Settings.js b/src/settings/Settings.js index 820329f6c6..cc45bbb4c7 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -478,11 +478,13 @@ export const SETTINGS = { deny: [], }, }, + // TODO: Remove setting: https://github.com/vector-im/riot-web/issues/14231 "RoomList.orderAlphabetically": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, displayName: _td("Order rooms by name"), default: false, }, + // TODO: Remove setting: https://github.com/vector-im/riot-web/issues/14231 "RoomList.orderByImportance": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, displayName: _td("Show rooms with unread notifications first"), diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index 497b8e5530..9edfef654c 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -50,8 +50,6 @@ export class RoomListStore2 extends AsyncStore { private tagWatcher = new TagWatcher(this); private readonly watchedSettings = [ - 'RoomList.orderAlphabetically', - 'RoomList.orderByImportance', 'feature_custom_tags', ]; @@ -338,11 +336,8 @@ export class RoomListStore2 extends AsyncStore { } private async updateAlgorithmInstances() { - const orderByImportance = SettingsStore.getValue("RoomList.orderByImportance"); - const orderAlphabetically = SettingsStore.getValue("RoomList.orderAlphabetically"); - - const defaultSort = orderAlphabetically ? SortAlgorithm.Alphabetic : SortAlgorithm.Recent; - const defaultOrder = orderByImportance ? ListAlgorithm.Importance : ListAlgorithm.Natural; + const defaultSort = SortAlgorithm.Alphabetic; + const defaultOrder = ListAlgorithm.Natural; for (const tag of Object.keys(this.orderedLists)) { const definedSort = this.getTagSorting(tag); From 0868af650a86bdf956cd25516c2ea47a47c0f68e Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 30 Jun 2020 19:37:00 -0600 Subject: [PATCH 224/294] Appease the linter --- .../views/settings/tabs/user/PreferencesUserSettingsTab.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index 592331f897..40b622cf37 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -38,7 +38,7 @@ export default class PreferencesUserSettingsTab extends React.Component { ]; // TODO: Remove temp structures: https://github.com/vector-im/riot-web/issues/14231 - static ELIGIBLE_ROOM_LIST_SETTINGS = () => { + static eligibleRoomListSettings = () => { if (RoomListStoreTempProxy.isUsingNewStore()) { return PreferencesUserSettingsTab.ROOM_LIST_2_SETTINGS; } @@ -189,7 +189,7 @@ export default class PreferencesUserSettingsTab extends React.Component {
          {_t("Room list")} - {this._renderGroup(PreferencesUserSettingsTab.ELIGIBLE_ROOM_LIST_SETTINGS())} + {this._renderGroup(PreferencesUserSettingsTab.eligibleRoomListSettings())}
          From ad982624fee59426e138ffb84af7b3aebf23ffde Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 30 Jun 2020 19:42:02 -0600 Subject: [PATCH 225/294] Remove context menu on invites in new room list Fixes https://github.com/vector-im/riot-web/issues/14198 For both, the decision has been made to not have a context menu pending further considerations of where invites should (and should not) be. Likely this will be dealt with in FTUE Notifications. --- src/components/views/rooms/RoomSublist2.tsx | 7 ++++++- src/components/views/rooms/RoomTile2.tsx | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index 58ebf54bf7..461098cb63 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -32,7 +32,7 @@ import StyledCheckbox from "../elements/StyledCheckbox"; import StyledRadioButton from "../elements/StyledRadioButton"; import RoomListStore from "../../../stores/room-list/RoomListStore2"; import { ListAlgorithm, SortAlgorithm } from "../../../stores/room-list/algorithms/models"; -import { TagID } from "../../../stores/room-list/models"; +import { DefaultTagID, TagID } from "../../../stores/room-list/models"; // 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 @@ -196,6 +196,11 @@ export default class RoomSublist2 extends React.Component { } private renderMenu(): React.ReactElement { + // TODO: Get a proper invite context menu, or take invites out of the room list. + if (this.props.tagId === DefaultTagID.Invite) { + return null; + } + let contextMenu = null; if (this.state.menuDisplayed) { const elementRect = this.menuButtonRef.current.getBoundingClientRect(); diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index 1284728855..2f1211608c 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -156,6 +156,11 @@ export default class RoomTile2 extends React.Component { private renderGeneralMenu(): React.ReactElement { if (this.props.isMinimized) return null; // no menu when minimized + // TODO: Get a proper invite context menu, or take invites out of the room list. + if (this.props.tag === DefaultTagID.Invite) { + return null; + } + let contextMenu = null; if (this.state.generalMenuDisplayed) { // The context menu appears within the list, so use the room tile as a reference point From 5a43acd42a1bc570611634b1e9c14abc35bb94d5 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 30 Jun 2020 19:51:28 -0600 Subject: [PATCH 226/294] Fix reaction event crashes in message previews Fixes https://github.com/vector-im/riot-web/issues/14224 --- src/stores/room-list/previews/ReactionEventPreview.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/stores/room-list/previews/ReactionEventPreview.ts b/src/stores/room-list/previews/ReactionEventPreview.ts index d58f592feb..07fac107ca 100644 --- a/src/stores/room-list/previews/ReactionEventPreview.ts +++ b/src/stores/room-list/previews/ReactionEventPreview.ts @@ -22,8 +22,11 @@ import { _t } from "../../../languageHandler"; export class ReactionEventPreview implements IPreview { public getTextFor(event: MatrixEvent, tagId?: TagID): string { - const reaction = event.getRelation().key; - if (!reaction) return; + const relation = event.getRelation(); + if (!relation) return null; // invalid reaction (probably redacted) + + const reaction = relation.key; + if (!reaction) return null; // invalid reaction (unknown format) if (isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) { return reaction; From 1889ee202bc77441e0219968550e68601fd53ac8 Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Wed, 1 Jul 2020 12:23:27 +0100 Subject: [PATCH 227/294] Add tooltips for breadcrumbs --- res/css/views/rooms/_RoomBreadcrumbs2.scss | 15 +++++++++++++++ src/components/views/rooms/RoomBreadcrumbs2.tsx | 7 +++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/res/css/views/rooms/_RoomBreadcrumbs2.scss b/res/css/views/rooms/_RoomBreadcrumbs2.scss index dd9581069c..6e5a5fbb16 100644 --- a/res/css/views/rooms/_RoomBreadcrumbs2.scss +++ b/res/css/views/rooms/_RoomBreadcrumbs2.scss @@ -51,3 +51,18 @@ limitations under the License. height: 32px; } } + +.mx_RoomBreadcrumbs2_Tooltip { + margin-left: -42px; + margin-top: -42px; + + &.mx_Tooltip { + background-color: $tagpanel-bg-color; + color: $accent-fg-color; + border: 0; + + .mx_Tooltip_chevron { + display: none; + } + } +} diff --git a/src/components/views/rooms/RoomBreadcrumbs2.tsx b/src/components/views/rooms/RoomBreadcrumbs2.tsx index c0417fc592..687f4dd73e 100644 --- a/src/components/views/rooms/RoomBreadcrumbs2.tsx +++ b/src/components/views/rooms/RoomBreadcrumbs2.tsx @@ -26,6 +26,7 @@ import { UPDATE_EVENT } from "../../../stores/AsyncStore"; import { CSSTransition } from "react-transition-group"; import RoomListStore from "../../../stores/room-list/RoomListStore2"; import { DefaultTagID } from "../../../stores/room-list/models"; +import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; // TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14231 // TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231 @@ -98,11 +99,13 @@ export default class RoomBreadcrumbs2 extends React.PureComponent this.viewRoom(r, i)} aria-label={_t("Room %(name)s", {name: r.name})} + title={r.name} + tooltipClassName={"mx_RoomBreadcrumbs2_Tooltip"} > - + ); }); From de7df7dcf9bafbe286c25a643cbe69785bb147d8 Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Wed, 1 Jul 2020 12:28:00 +0100 Subject: [PATCH 228/294] Lint --- src/components/views/elements/AccessibleTooltipButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/elements/AccessibleTooltipButton.tsx b/src/components/views/elements/AccessibleTooltipButton.tsx index 1c0e18c399..f4d63136e1 100644 --- a/src/components/views/elements/AccessibleTooltipButton.tsx +++ b/src/components/views/elements/AccessibleTooltipButton.tsx @@ -33,7 +33,7 @@ interface IState { export default class AccessibleTooltipButton extends React.PureComponent { constructor(props: ITooltipProps) { - super(props) + super(props); this.state = { hover: false, }; From 1dd9c1eea3b420fba4dc34e034e265c90a6af9bf Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Wed, 1 Jul 2020 12:28:32 +0100 Subject: [PATCH 229/294] Use avatar sisze inplace --- src/components/views/rooms/RoomTile2.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index c36f504409..ed0044bacb 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -232,10 +232,9 @@ export default class RoomTile2 extends React.Component { 'mx_RoomTile2_minimized': this.props.isMinimized, }); - const avatarSize = 32; const roomAvatar = ; From b5014282a4ed02ca8c186d95a0093f75bba07e11 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 1 Jul 2020 13:59:50 +0100 Subject: [PATCH 230/294] Iterate PR based on Figma design and feedback Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/_common.scss | 1 + res/css/views/rooms/_RoomTile2.scss | 5 +++++ src/components/structures/UserMenu.tsx | 1 + src/components/views/rooms/RoomTile2.tsx | 7 ++++--- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/res/css/_common.scss b/res/css/_common.scss index 880b01a10e..6e70618142 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -631,6 +631,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { } .mx_AccessibleButton { + // pad the inside of the button so that the hover background is padded too padding-top: 12px; padding-bottom: 12px; text-decoration: none; diff --git a/res/css/views/rooms/_RoomTile2.scss b/res/css/views/rooms/_RoomTile2.scss index 8933c73045..44c5b6ee17 100644 --- a/res/css/views/rooms/_RoomTile2.scss +++ b/res/css/views/rooms/_RoomTile2.scss @@ -92,6 +92,7 @@ limitations under the License. justify-content: center; } + // The context menu buttons are hidden by default .mx_RoomTile2_menuButton, .mx_RoomTile2_notificationsButton { width: 20px; @@ -114,6 +115,7 @@ limitations under the License. } } + // If the room has an overriden notification setting then we always show the notifications menu button .mx_RoomTile2_notificationsButton.mx_RoomTile2_notificationsButton_show { display: block; } @@ -166,6 +168,9 @@ limitations under the License. .mx_RoomTile2_iconBellCrossed::before { mask-image: url('$(res)/img/feather-customised/bell-crossed.svg'); } +.mx_RoomTile2_iconBellMentions::before { + mask-image: url('$(res)/img/feather-customised/bell-mentions.custom.svg'); +} .mx_RoomTile2_iconCheck::before { mask-image: url('$(res)/img/feather-customised/check.svg'); } diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 4a45162902..d6771f3011 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -202,6 +202,7 @@ export default class UserMenu extends React.Component { return ( { const left = elementRect.left + window.pageXOffset - 6; - let top = elementRect.bottom + window.pageYOffset + 21; + let top = elementRect.bottom + window.pageYOffset + 17; const chevronFace = "none"; return {left, top, chevronFace}; }; @@ -209,10 +209,11 @@ export default class RoomTile2 extends React.Component { if (MatrixClientPeg.get().isGuest()) return; try { - // TODO add local echo + // TODO add local echo - https://github.com/vector-im/riot-web/issues/14280 await setRoomNotifsState(this.props.room.roomId, newState); } catch (error) { // TODO: some form of error notification to the user to inform them that their state change failed. + // https://github.com/vector-im/riot-web/issues/14281 console.error(error); } @@ -254,7 +255,7 @@ export default class RoomTile2 extends React.Component { Date: Wed, 1 Jul 2020 02:22:32 +0000 Subject: [PATCH 231/294] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2355 of 2355 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index 8f4d9b12d3..0400771bfb 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2593,5 +2593,25 @@ "%(senderName)s changed the room topic": "%(senderName)s 變更了聊天室主題", "New spinner design": "新的微調器設計", "Use a more compact ‘Modern’ layout": "使用更簡潔的「現代」佈局", - "Message deleted on %(date)s": "訊息刪除於 %(date)s" + "Message deleted on %(date)s": "訊息刪除於 %(date)s", + "Wrong file type": "錯誤的檔案類型", + "Wrong Recovery Key": "錯誤的復原金鑰", + "Invalid Recovery Key": "無效的復原金鑰", + "Security Phrase": "安全密語", + "Enter your Security Phrase or to continue.": "輸入您的安全密語或以繼續。", + "Security Key": "安全金鑰", + "Use your Security Key to continue.": "使用您的安全金鑰以繼續。", + "Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.": "透過備份您伺服器上的加密金鑰來防止失去對您已加密的訊息與資料的存取權。", + "Generate a Security Key": "生成加密金鑰", + "We’ll generate a Security Key for you to store somewhere safe, like a password manager or a safe.": "我們將會為您生成一把安全金鑰,供您存放在安全的地方,如密碼管理員或保險櫃等。", + "Enter a Security Phrase": "輸入安全密語", + "Use a secret phrase only you know, and optionally save a Security Key to use for backup.": "使用僅有您知道的祕密短語,並選擇性地儲存安全金鑰以供備用。", + "Enter a security phrase only you know, as it’s used to safeguard your data. To be secure, you shouldn’t re-use your account password.": "輸入僅有您知道的安全短語,其用於保護您的資料。安全起見,請勿重複使用您的帳號密碼。", + "Store your Security Key somewhere safe, like a password manager or a safe, as it’s used to safeguard your encrypted data.": "將您的安全金鑰存放在某個安全的地方,如密碼管理員或保險櫃,其用於保護您的加密資料。", + "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.": "如果您現在取消,在您失去對您的登入的存取權時可能會遺失已加密的訊息與資料。", + "You can also set up Secure Backup & manage your keys in Settings.": "您也可以在設定中設定安全備份並管理您的金鑰。", + "Set up Secure backup": "設定安全備份", + "Set a Security Phrase": "設定安全密語", + "Confirm Security Phrase": "確認安全密語", + "Save your Security Key": "儲存您的安全金鑰" } From 2db0bb570a15fadc758e0235d69f161053376615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Wed, 1 Jul 2020 07:48:14 +0000 Subject: [PATCH 232/294] Translated using Weblate (French) Currently translated at 100.0% (2355 of 2355 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/fr/ --- src/i18n/strings/fr.json | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index bc0cccbe07..3beb6ea836 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -2594,5 +2594,25 @@ "You changed the room topic": "Vous avez changé le sujet du salon", "%(senderName)s changed the room topic": "%(senderName)s a changé le sujet du salon", "New spinner design": "Nouveau design du spinner", - "Message deleted on %(date)s": "Message supprimé le %(date)s" + "Message deleted on %(date)s": "Message supprimé le %(date)s", + "Wrong file type": "Mauvais type de fichier", + "Wrong Recovery Key": "Mauvaise clé de récupération", + "Invalid Recovery Key": "Clé de récupération non valide", + "Security Phrase": "Phrase de sécurité", + "Enter your Security Phrase or to continue.": "Saisissez votre phrase de sécurité ou pour continuer.", + "Security Key": "Clé de sécurité", + "Use your Security Key to continue.": "Utilisez votre clé de sécurité pour continuer.", + "Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.": "Protection afin d’éviter de perdre l’accès aux messages et données chiffrés en sauvegardant les clés de chiffrement sur votre serveur.", + "Generate a Security Key": "Générer une clé de sécurité", + "We’ll generate a Security Key for you to store somewhere safe, like a password manager or a safe.": "Nous génèrerons une clé de sécurité que vous devrez stocker dans un endroit sûr, comme un gestionnaire de mots de passe ou un coffre.", + "Enter a Security Phrase": "Saisir une phrase de sécurité", + "Use a secret phrase only you know, and optionally save a Security Key to use for backup.": "Utilisez une phrase secrète que vous êtes seul·e à connaître et enregistrez éventuellement une clé de sécurité à utiliser pour la sauvegarde.", + "Enter a security phrase only you know, as it’s used to safeguard your data. To be secure, you shouldn’t re-use your account password.": "Saisissez une phrase de sécurité que vous seul·e connaissez, car elle est utilisée pour protéger vos données. Pour plus de sécurité, vous ne devriez pas réutiliser le mot de passe de votre compte.", + "Store your Security Key somewhere safe, like a password manager or a safe, as it’s used to safeguard your encrypted data.": "Stockez votre clé de sécurité dans un endroit sûr, comme un gestionnaire de mots de passe ou un coffre, car elle est utilisée pour protéger vos données chiffrées.", + "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.": "Si vous annulez maintenant, vous pourriez perdre vos messages et données chiffrés si vous perdez l’accès à vos identifiants.", + "You can also set up Secure Backup & manage your keys in Settings.": "Vous pouvez aussi configurer la sauvegarde sécurisée et gérer vos clés depuis les paramètres.", + "Set up Secure backup": "Configurer la sauvegarde sécurisée", + "Set a Security Phrase": "Définir une phrase de sécurité", + "Confirm Security Phrase": "Confirmer la phrase de sécurité", + "Save your Security Key": "Sauvegarder votre clé de sécurité" } From 5ca4d352eec6c6c84e95a83dcde6254bf7944ada Mon Sep 17 00:00:00 2001 From: XoseM Date: Wed, 1 Jul 2020 05:16:00 +0000 Subject: [PATCH 233/294] Translated using Weblate (Galician) Currently translated at 100.0% (2355 of 2355 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/gl/ --- src/i18n/strings/gl.json | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index fa8e11863f..00ef29f9fd 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -2425,5 +2425,26 @@ "%(senderName)s invited %(targetName)s": "%(senderName)s convidou a %(targetName)s", "You changed the room topic": "Cambiaches o tema da sala", "%(senderName)s changed the room topic": "%(senderName)s cambiou o asunto da sala", - "Message deleted on %(date)s": "Mensaxe eliminada o %(date)s" + "Message deleted on %(date)s": "Mensaxe eliminada o %(date)s", + "Wrong file type": "Tipo de ficheiro erróneo", + "Looks good!": "Pinta ben!", + "Wrong Recovery Key": "Chave de recuperación errónea", + "Invalid Recovery Key": "Chave de recuperación non válida", + "Security Phrase": "Frase de seguridade", + "Enter your Security Phrase or to continue.": "Escribe a túa Frase de Seguridade ou para continuar.", + "Security Key": "Chave de Seguridade", + "Use your Security Key to continue.": "Usa a túa Chave de Seguridade para continuar.", + "Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.": "Protección contra a perda do acceso ás mensaxes cifradas e datos facendo unha copia de apoio das chaves no servidor.", + "Generate a Security Key": "Crear unha Chave de Seguridade", + "We’ll generate a Security Key for you to store somewhere safe, like a password manager or a safe.": "Crearemos unha Chave de Seguridade para que a gardes nalgún lugar seguro, como un xestor de contrasinais ou caixa de seguridade.", + "Enter a Security Phrase": "Escribe unha Frase de Seguridade", + "Use a secret phrase only you know, and optionally save a Security Key to use for backup.": "Usa unha frase segreda que só ti coñezas, e de xeito optativo unha Chave de Seguridade para usar como apoio.", + "Enter a security phrase only you know, as it’s used to safeguard your data. To be secure, you shouldn’t re-use your account password.": "Escribe unha frase de seguridade que só ti coñezas, será utilizada para protexer os teus datos. Para maior seguridade, non deberías reutilizar o contrasinal da conta.", + "Store your Security Key somewhere safe, like a password manager or a safe, as it’s used to safeguard your encrypted data.": "Garda a Chave de Seguridade nalgún lugar seguro, como un xestor de contrasinais ou caixa de seguridade, será utiizada para protexer os teus datos cifrados.", + "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.": "Se cancelas agora, poderías perder mensaxes e datos cifrados se perdes o acceso ós datos de conexión.", + "You can also set up Secure Backup & manage your keys in Settings.": "Podes configurar a Copia de apoio Segura e xestionar as chaves en Axustes.", + "Set up Secure backup": "Configurar Copia de apoio Segura", + "Set a Security Phrase": "Establece a Frase de Seguridade", + "Confirm Security Phrase": "Confirma a Frase de Seguridade", + "Save your Security Key": "Garda a Chave de Seguridade" } From 1e81049801c6dd1aec0c0d53c4518ac03e374033 Mon Sep 17 00:00:00 2001 From: Michael Albert Date: Wed, 1 Jul 2020 11:49:01 +0000 Subject: [PATCH 234/294] Translated using Weblate (German) Currently translated at 99.2% (2336 of 2355 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 4786845418..4b305cd5b6 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -2533,5 +2533,8 @@ "%(senderName)s changed the room topic": "%(senderName)s hat das Raumthema geändert", "New spinner design": "Neue Warteanimation", "Use a more compact ‘Modern’ layout": "Verwende ein kompakteres 'modernes' Layout", - "Message deleted on %(date)s": "Nachricht am %(date)s gelöscht" + "Message deleted on %(date)s": "Nachricht am %(date)s gelöscht", + "Wrong file type": "Falscher Dateityp", + "Wrong Recovery Key": "Falscher Wiederherstellungsschlüssel", + "Invalid Recovery Key": "Ungültiger Wiederherstellungsschlüssel" } From 9434923da399c7a4e1664c63fcfa9b68ef0b822b Mon Sep 17 00:00:00 2001 From: strix aluco Date: Wed, 1 Jul 2020 03:52:28 +0000 Subject: [PATCH 235/294] Translated using Weblate (Ukrainian) Currently translated at 26.2% (618 of 2355 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/uk/ --- src/i18n/strings/uk.json | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/i18n/strings/uk.json b/src/i18n/strings/uk.json index fb6657d6c1..207ed0a8e6 100644 --- a/src/i18n/strings/uk.json +++ b/src/i18n/strings/uk.json @@ -600,8 +600,8 @@ "Language and region": "Мова та регіон", "Account management": "Керування обліківкою", "Deactivating your account is a permanent action - be careful!": "Деактивація вашої обліківки є безповоротною дією — будьте обережні!", - "Deactivate Account": "Деактивувати обліківку", - "Deactivate account": "Деактивувати обліківку", + "Deactivate Account": "Знедіяти обліківку", + "Deactivate account": "Знедіяти обліківку", "Legal": "Правова інформація", "Credits": "Подяки", "For help with using Riot, click here.": "Якщо необхідна допомога у користуванні Riot'ом, клацніть тут.", @@ -624,7 +624,7 @@ "Link this email with your account in Settings to receive invites directly in Riot.": "Зв'яжіть цю е-пошту з вашою обліківкою у Налаштуваннях щоб отримувати сповіщення прямо у Riot.", "This invite to %(roomName)s was sent to %(email)s": "Це запрошення до %(roomName)s було надіслане на %(email)s", "Use an identity server in Settings to receive invites directly in Riot.": "Використовувати сервер ідентифікації у Налаштуваннях щоб отримувати запрошення прямо у Riot.", - "Are you sure you want to deactivate your account? This is irreversible.": "Ви впевнені у тому, що бажаєте деактивувати вашу обліківку? Це є безповоротним.", + "Are you sure you want to deactivate your account? This is irreversible.": "Ви впевнені у тому, що бажаєте знедіяти вашу обліківку? Це є безповоротним.", "Confirm account deactivation": "Підтвердьте деактивацію обліківки", "To continue, please enter your password:": "Щоб продовжити, введіть, будь ласка, ваш пароль:", "This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. This action is irreversible.": "Ваша обліківка стане назавжди невикористовною. Ви не матимете змоги увійти в неї і ніхто не зможе перереєструватись під цим користувацьким ID. Це призведе до виходу вашої обліківки з усіх кімнат та до видалення деталей вашої обліківки з вашого серверу ідентифікації. Ця дія є безповоротною.", @@ -660,5 +660,12 @@ "Room": "Кімната", "Failed to reject invite": "Не вдалось відхилити запрошення", "You have %(count)s unread notifications in a prior version of this room.|other": "Ви маєте %(count)s непрочитаних сповіщень у попередній версії цієї кімнати.", - "You have %(count)s unread notifications in a prior version of this room.|one": "У вас одне непрочитане сповіщення у попередній версії цієї кімнати." + "You have %(count)s unread notifications in a prior version of this room.|one": "У вас одне непрочитане сповіщення у попередній версії цієї кімнати.", + "Deactivate user?": "Знедіяти користувача?", + "Deactivating this user will log them out and prevent them from logging back in. Additionally, they will leave all the rooms they are in. This action cannot be reversed. Are you sure you want to deactivate this user?": "Знедіювання цього користувача виведе їх з системи і унеможливить вхід у майбутньому. До того ж, вони залишать усі кімнати, в яких перебувають. Ця дія є безповоротною. Ви впевнені, що хочете знедіяти цього користувача?", + "Deactivate user": "Знедіяти користувача", + "Failed to deactivate user": "Не вдалось знедіяти користувача", + "Deactivating your account does not by default cause us to forget messages you have sent. If you would like us to forget your messages, please tick the box below.": "Знедіювання вашої обліківки типово не призводить до забуття надісланих вами повідомлень. Якщо ви бажаєте щоб ми забули ваші повідомлення, поставте прапорець внизу.", + "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.": "Видність повідомлень у Matrix є схожою до е-пошти. Забування нами ваших повідомлень означає, що надіслані вами повідомлення не будуть поширені будь-яким новим чи незареєстрованим користувачам, але зареєстровані користувачі, які мають доступ до цих повідомлень, і надалі матимуть доступ до їхніх копій.", + "Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)": "Забудьте, будь ласка, усі надіслані мною повідомлення після знедіювання моєї обліківки. (Попередження: після цього майбутні користувачі бачитимуть неповні бесіди)" } From 3b7b6eca6f163618c1806ab7cb88a020bfb7ac94 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 1 Jul 2020 14:05:33 +0100 Subject: [PATCH 236/294] align context menus even better 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 d518c74aa7..e12be8b30e 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -69,7 +69,8 @@ interface IState { } const contextMenuBelow = (elementRect) => { - const left = elementRect.left + window.pageXOffset - 6; + // align the context menu's icons with the icon which opened the context menu + const left = elementRect.left + window.pageXOffset - 9; let top = elementRect.bottom + window.pageYOffset + 17; const chevronFace = "none"; return {left, top, chevronFace}; From 161c37d93ccedf63ea7fe056781667e32cc4dc5c Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 1 Jul 2020 14:21:35 +0100 Subject: [PATCH 237/294] 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 0cbc506ad615aaa5bd37e732edf9712021c9a175 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 1 Jul 2020 14:26:50 +0100 Subject: [PATCH 238/294] add missing svg Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/img/feather-customised/bell-mentions.custom.svg | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 res/img/feather-customised/bell-mentions.custom.svg diff --git a/res/img/feather-customised/bell-mentions.custom.svg b/res/img/feather-customised/bell-mentions.custom.svg new file mode 100644 index 0000000000..fcc02f337f --- /dev/null +++ b/res/img/feather-customised/bell-mentions.custom.svg @@ -0,0 +1,3 @@ + + + From 9831698b1e004f7166e64c83a06583a1a162b9a8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 1 Jul 2020 14:28:00 +0100 Subject: [PATCH 239/294] Hide notifications menu from invite tiles Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/RoomTile2.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index 882bd21084..c9a1f39982 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -230,7 +230,10 @@ export default class RoomTile2 extends React.Component { private onClickMute = ev => this.saveNotifState(ev, MUTE); private renderNotificationsMenu(): React.ReactElement { - if (this.props.isMinimized || MatrixClientPeg.get().isGuest()) return null; // no menu when minimized or guest + 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; + } const state = getRoomNotifsState(this.props.room.roomId); From 012354239d6aa48277d873fff5574031c9d4b632 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 1 Jul 2020 14:32:19 +0100 Subject: [PATCH 240/294] 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 241/294] 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 ad27dbbfab6e7d2c3cdc07fc48bbad237a9b0465 Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Wed, 1 Jul 2020 15:15:18 +0100 Subject: [PATCH 242/294] Clean up classnames --- src/components/structures/LeftPanel2.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index d34986f981..ab117d55ed 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -15,7 +15,6 @@ limitations under the License. */ import * as React from "react"; -import classnames from 'classnames'; import { createRef } from "react"; import TagPanel from "./TagPanel"; import classNames from "classnames"; @@ -206,7 +205,7 @@ export default class LeftPanel2 extends React.Component { "mx_LeftPanel2_minimized": this.props.isMinimized, }); - const className = classnames( + const roomListClasses = classNames( "mx_LeftPanel2_actualRoomListContainer", "mx_AutoHideScrollbar", ); @@ -218,7 +217,7 @@ export default class LeftPanel2 extends React.Component { {this.renderHeader()} {this.renderSearchExplore()}
          {roomList}
          From 2162517a37cae3f5ea6a422d14c5d2f1429b6939 Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Wed, 1 Jul 2020 16:05:27 +0100 Subject: [PATCH 243/294] Display breadcrumbs only after 20 rooms have been joined --- src/components/structures/LeftPanel2.tsx | 3 +++ src/stores/BreadcrumbsStore.ts | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index b4ec897561..fb07c9c601 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -30,6 +30,7 @@ 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"; // 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 @@ -69,6 +70,7 @@ export default class LeftPanel2 extends React.Component { }; BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate); + RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate); this.tagPanelWatcherRef = SettingsStore.watchSetting("TagPanel.enableTagPanel", null, () => { this.setState({showTagPanel: SettingsStore.getValue("TagPanel.enableTagPanel")}); }); @@ -81,6 +83,7 @@ export default class LeftPanel2 extends React.Component { public componentWillUnmount() { SettingsStore.unwatchSetting(this.tagPanelWatcherRef); BreadcrumbsStore.instance.off(UPDATE_EVENT, this.onBreadcrumbsUpdate); + RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate); this.props.resizeNotifier.off("middlePanelResizedNoisy", this.onResize); } diff --git a/src/stores/BreadcrumbsStore.ts b/src/stores/BreadcrumbsStore.ts index 1c47075cbb..43bef8a538 100644 --- a/src/stores/BreadcrumbsStore.ts +++ b/src/stores/BreadcrumbsStore.ts @@ -21,6 +21,7 @@ import { AsyncStoreWithClient } from "./AsyncStoreWithClient"; import defaultDispatcher from "../dispatcher/dispatcher"; import { arrayHasDiff } from "../utils/arrays"; import { RoomListStoreTempProxy } from "./room-list/RoomListStoreTempProxy"; +import _reduce from 'lodash/reduce'; const MAX_ROOMS = 20; // arbitrary const AUTOJOIN_WAIT_THRESHOLD_MS = 90000; // 90s, the time we wait for an autojoined room to show up @@ -51,7 +52,10 @@ export class BreadcrumbsStore extends AsyncStoreWithClient { } public get visible(): boolean { - return this.state.enabled; + // @ts-ignore - TypeScript really wants this to be [tagId: string] but we know better. + const roomCount = _reduce(RoomListStoreTempProxy.getRoomLists(), (result, rooms) => result + rooms.length, 0) + console.log(`calculating roomlist size: ${roomCount}`) + return roomCount >= 20; } protected async onAction(payload: ActionPayload) { From d203943b7fd9b7877767db3de3684a6e671a2d84 Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Wed, 1 Jul 2020 16:07:27 +0100 Subject: [PATCH 244/294] lint semis --- src/stores/BreadcrumbsStore.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/stores/BreadcrumbsStore.ts b/src/stores/BreadcrumbsStore.ts index 43bef8a538..9905dd4345 100644 --- a/src/stores/BreadcrumbsStore.ts +++ b/src/stores/BreadcrumbsStore.ts @@ -53,8 +53,7 @@ export class BreadcrumbsStore extends AsyncStoreWithClient { public get visible(): boolean { // @ts-ignore - TypeScript really wants this to be [tagId: string] but we know better. - const roomCount = _reduce(RoomListStoreTempProxy.getRoomLists(), (result, rooms) => result + rooms.length, 0) - console.log(`calculating roomlist size: ${roomCount}`) + const roomCount = _reduce(RoomListStoreTempProxy.getRoomLists(), (result, rooms) => result + rooms.length, 0); return roomCount >= 20; } From 946fde5cc5fd24daa1ea7b66385112b17fad36a5 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 1 Jul 2020 11:59:32 -0600 Subject: [PATCH 245/294] Be consistent in visible tiles usage --- src/components/views/rooms/RoomSublist2.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index 6682527254..fb955fcd57 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -370,7 +370,7 @@ export default class RoomSublist2 extends React.Component { // floats above the resize handle, if we have one present. If the user has all // tiles visible, it becomes 'show less'. let showNButton = null; - if (this.numTiles > this.numVisibleTiles) { + if (this.numTiles > visibleTiles.length) { // we have a cutoff condition - add the button to show all const numMissing = this.numTiles - visibleTiles.length; let showMoreText = ( @@ -387,7 +387,7 @@ export default class RoomSublist2 extends React.Component { {showMoreText}
          ); - } else if (this.numTiles <= this.numVisibleTiles && this.numTiles > this.props.layout.defaultVisibleTiles) { + } else if (this.numTiles <= visibleTiles.length && this.numTiles > this.props.layout.defaultVisibleTiles) { // we have all tiles visible - add a button to show less let showLessText = ( From 89bd572371e695e861b6d76c23943c1193ff65da Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 1 Jul 2020 23:05:37 +0100 Subject: [PATCH 246/294] Fix context menu nesting causing bubbling and instabilities Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/ContextMenu.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/structures/ContextMenu.js b/src/components/structures/ContextMenu.js index 98b0867ccc..a56a987fcf 100644 --- a/src/components/structures/ContextMenu.js +++ b/src/components/structures/ContextMenu.js @@ -116,6 +116,7 @@ export class ContextMenu extends React.Component { this.props.onFinished(); e.preventDefault(); + e.stopPropagation(); const x = e.clientX; const y = e.clientY; @@ -133,6 +134,12 @@ export class ContextMenu extends React.Component { } }; + onContextMenuPreventBubbling = (e) => { + // stop propagation so that any context menu handlers don't leak out of this context menu + // but do not inhibit the default browser menu + e.stopPropagation(); + }; + _onMoveFocus = (element, up) => { let descending = false; // are we currently descending or ascending through the DOM tree? @@ -324,7 +331,7 @@ export class ContextMenu extends React.Component { } return ( -
          +
          { chevron } { props.children } From 5c2b291510aa2cbacd7785b0d9a37eca0844121f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 1 Jul 2020 23:06:26 +0100 Subject: [PATCH 247/294] Support right click context menu interactions on Room List 2 Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/UserMenu.tsx | 40 ++++++++---- src/components/views/rooms/RoomSublist2.tsx | 42 ++++++++----- src/components/views/rooms/RoomTile2.tsx | 70 +++++++++++---------- 3 files changed, 92 insertions(+), 60 deletions(-) diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index d6771f3011..92a4666a9d 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -42,8 +42,10 @@ interface IProps { isMinimized: boolean; } +type PartialDOMRect = Pick; + interface IState { - menuDisplayed: boolean; + contextMenuPosition: PartialDOMRect; isDarkTheme: boolean; } @@ -56,7 +58,7 @@ export default class UserMenu extends React.Component { super(props); this.state = { - menuDisplayed: false, + contextMenuPosition: null, isDarkTheme: this.isUserOnDarkTheme(), }; @@ -106,13 +108,27 @@ export default class UserMenu extends React.Component { private onOpenMenuClick = (ev: InputEvent) => { ev.preventDefault(); ev.stopPropagation(); - this.setState({menuDisplayed: true}); + const target = ev.target as HTMLButtonElement; + this.setState({contextMenuPosition: target.getBoundingClientRect()}); + }; + + private onContextMenu = (ev: React.MouseEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + this.setState({ + contextMenuPosition: { + left: ev.clientX, + top: ev.clientY, + width: 20, + height: 0, + }, + }); }; private onCloseMenu = (ev: InputEvent) => { ev.preventDefault(); ev.stopPropagation(); - this.setState({menuDisplayed: false}); + this.setState({contextMenuPosition: null}); }; private onSwitchThemeClick = () => { @@ -129,7 +145,7 @@ export default class UserMenu extends React.Component { const payload: OpenToTabPayload = {action: Action.ViewUserSettings, initialTabId: tabId}; defaultDispatcher.dispatch(payload); - this.setState({menuDisplayed: false}); // also close the menu + this.setState({contextMenuPosition: null}); // also close the menu }; private onShowArchived = (ev: ButtonEvent) => { @@ -145,7 +161,7 @@ export default class UserMenu extends React.Component { ev.stopPropagation(); Modal.createTrackedDialog('Report bugs & give feedback', '', RedesignFeedbackDialog); - this.setState({menuDisplayed: false}); // also close the menu + this.setState({contextMenuPosition: null}); // also close the menu }; private onSignOutClick = (ev: ButtonEvent) => { @@ -153,7 +169,7 @@ export default class UserMenu extends React.Component { ev.stopPropagation(); Modal.createTrackedDialog('Logout from LeftPanel', '', LogoutDialog); - this.setState({menuDisplayed: false}); // also close the menu + this.setState({contextMenuPosition: null}); // also close the menu }; private onHomeClick = (ev: ButtonEvent) => { @@ -164,7 +180,7 @@ export default class UserMenu extends React.Component { }; private renderContextMenu = (): React.ReactNode => { - if (!this.state.menuDisplayed) return null; + if (!this.state.contextMenuPosition) return null; let hostingLink; const signupLink = getHostingLink("user-context-menu"); @@ -198,13 +214,12 @@ export default class UserMenu extends React.Component { ); } - const elementRect = this.buttonRef.current.getBoundingClientRect(); return (
          @@ -290,7 +305,8 @@ export default class UserMenu extends React.Component { onClick={this.onOpenMenuClick} inputRef={this.buttonRef} label={_t("Account settings")} - isExpanded={this.state.menuDisplayed} + isExpanded={!!this.state.contextMenuPosition} + onContextMenu={this.onContextMenu} >
          diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index 461098cb63..36d38c7087 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -65,22 +65,21 @@ interface IProps { // TODO: Account for https://github.com/vector-im/riot-web/issues/14179 } +type PartialDOMRect = Pick; + interface IState { notificationState: ListNotificationState; - menuDisplayed: boolean; + contextMenuPosition: PartialDOMRect; isResizing: boolean; } export default class RoomSublist2 extends React.Component { - private headerButton = createRef(); - private menuButtonRef: React.RefObject = createRef(); - constructor(props: IProps) { super(props); this.state = { notificationState: new ListNotificationState(this.props.isInvite, this.props.tagId), - menuDisplayed: false, + contextMenuPosition: null, isResizing: false, }; this.state.notificationState.setRooms(this.props.rooms); @@ -132,11 +131,24 @@ export default class RoomSublist2 extends React.Component { private onOpenMenuClick = (ev: InputEvent) => { ev.preventDefault(); ev.stopPropagation(); - this.setState({menuDisplayed: true}); + const target = ev.target as HTMLButtonElement; + this.setState({contextMenuPosition: target.getBoundingClientRect()}); + }; + + private onContextMenu = (ev: React.MouseEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + this.setState({ + contextMenuPosition: { + left: ev.clientX, + top: ev.clientY, + height: 0, + }, + }); }; private onCloseMenu = () => { - this.setState({menuDisplayed: false}); + this.setState({contextMenuPosition: null}); }; private onUnreadFirstChanged = async () => { @@ -202,15 +214,14 @@ export default class RoomSublist2 extends React.Component { } let contextMenu = null; - if (this.state.menuDisplayed) { - const elementRect = this.menuButtonRef.current.getBoundingClientRect(); + if (this.state.contextMenuPosition) { const isAlphabetical = RoomListStore.instance.getTagSorting(this.props.tagId) === SortAlgorithm.Alphabetic; const isUnreadFirst = RoomListStore.instance.getListOrder(this.props.tagId) === ListAlgorithm.Importance; contextMenu = (
          @@ -261,9 +272,8 @@ export default class RoomSublist2 extends React.Component { {contextMenu} @@ -272,7 +282,7 @@ export default class RoomSublist2 extends React.Component { private renderHeader(): React.ReactElement { return ( - + {({onFocus, isActive, ref}) => { // TODO: Use onFocus: https://github.com/vector-im/riot-web/issues/14180 const tabIndex = isActive ? 0 : -1; @@ -317,12 +327,14 @@ export default class RoomSublist2 extends React.Component {
          {this.props.label} @@ -347,7 +359,7 @@ export default class RoomSublist2 extends React.Component { const classes = classNames({ 'mx_RoomSublist2': true, - 'mx_RoomSublist2_hasMenuOpen': this.state.menuDisplayed, + 'mx_RoomSublist2_hasMenuOpen': !!this.state.contextMenuPosition, 'mx_RoomSublist2_minimized': this.props.isMinimized, }); diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index c9a1f39982..47b5b4206b 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -60,15 +60,17 @@ interface IProps { // TODO: Incoming call boxes: https://github.com/vector-im/riot-web/issues/14177 } +type PartialDOMRect = Pick; + interface IState { hover: boolean; notificationState: INotificationState; selected: boolean; - notificationsMenuDisplayed: boolean; - generalMenuDisplayed: boolean; + notificationsMenuPosition: PartialDOMRect; + generalMenuPosition: PartialDOMRect; } -const contextMenuBelow = (elementRect) => { +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; let top = elementRect.bottom + window.pageYOffset + 17; @@ -103,9 +105,6 @@ const NotifOption: React.FC = ({active, onClick, iconClassNam }; export default class RoomTile2 extends React.Component { - private notificationsMenuButtonRef: React.RefObject = createRef(); - private generalMenuButtonRef: React.RefObject = createRef(); - // TODO: a11y: https://github.com/vector-im/riot-web/issues/14180 constructor(props: IProps) { @@ -115,8 +114,8 @@ export default class RoomTile2 extends React.Component { hover: false, notificationState: new TagSpecificNotificationState(this.props.room, this.props.tag), selected: ActiveRoomObserver.activeRoomId === this.props.room.roomId, - notificationsMenuDisplayed: false, - generalMenuDisplayed: false, + notificationsMenuPosition: null, + generalMenuPosition: null, }; ActiveRoomObserver.addListener(this.props.room.roomId, this.onActiveRoomUpdate); @@ -137,6 +136,8 @@ export default class RoomTile2 extends React.Component { }; private onTileClick = (ev: React.KeyboardEvent) => { + ev.preventDefault(); + ev.stopPropagation(); dis.dispatch({ action: 'view_room', // TODO: Support show_room_tile in new room list: https://github.com/vector-im/riot-web/issues/14233 @@ -153,25 +154,34 @@ export default class RoomTile2 extends React.Component { private onNotificationsMenuOpenClick = (ev: InputEvent) => { ev.preventDefault(); ev.stopPropagation(); - this.setState({notificationsMenuDisplayed: true}); + const target = ev.target as HTMLButtonElement; + this.setState({notificationsMenuPosition: target.getBoundingClientRect()}); }; - private onCloseNotificationsMenu = (ev: InputEvent) => { - ev.preventDefault(); - ev.stopPropagation(); - this.setState({notificationsMenuDisplayed: false}); + private onCloseNotificationsMenu = () => { + this.setState({notificationsMenuPosition: null}); }; private onGeneralMenuOpenClick = (ev: InputEvent) => { ev.preventDefault(); ev.stopPropagation(); - this.setState({generalMenuDisplayed: true}); + const target = ev.target as HTMLButtonElement; + this.setState({generalMenuPosition: target.getBoundingClientRect()}); }; - private onCloseGeneralMenu = (ev: InputEvent) => { + private onContextMenu = (ev: React.MouseEvent) => { ev.preventDefault(); ev.stopPropagation(); - this.setState({generalMenuDisplayed: false}); + this.setState({ + generalMenuPosition: { + left: ev.clientX, + bottom: ev.clientY, + }, + }); + }; + + private onCloseGeneralMenu = () => { + this.setState({generalMenuPosition: null}); }; private onTagRoom = (ev: ButtonEvent, tagId: TagID) => { @@ -190,7 +200,7 @@ export default class RoomTile2 extends React.Component { action: 'leave_room', room_id: this.props.room.roomId, }); - this.setState({generalMenuDisplayed: false}); // hide the menu + this.setState({generalMenuPosition: null}); // hide the menu }; private onOpenRoomSettings = (ev: ButtonEvent) => { @@ -201,7 +211,7 @@ export default class RoomTile2 extends React.Component { action: 'open_room_settings', room_id: this.props.room.roomId, }); - this.setState({generalMenuDisplayed: false}); // hide the menu + this.setState({generalMenuPosition: null}); // hide the menu }; private async saveNotifState(ev: ButtonEvent, newState: ALL_MESSAGES_LOUD | ALL_MESSAGES | MENTIONS_ONLY | MUTE) { @@ -218,10 +228,7 @@ export default class RoomTile2 extends React.Component { console.error(error); } - // Close the context menu - this.setState({ - notificationsMenuDisplayed: false, - }); + this.setState({notificationsMenuPosition: null}); // Close the context menu } private onClickAllNotifs = ev => this.saveNotifState(ev, ALL_MESSAGES); @@ -238,10 +245,9 @@ export default class RoomTile2 extends React.Component { const state = getRoomNotifsState(this.props.room.roomId); let contextMenu = null; - if (this.state.notificationsMenuDisplayed) { - const elementRect = this.notificationsMenuButtonRef.current.getBoundingClientRect(); + if (this.state.notificationsMenuPosition) { contextMenu = ( - +
          { {contextMenu} @@ -307,10 +312,9 @@ export default class RoomTile2 extends React.Component { } let contextMenu = null; - if (this.state.generalMenuDisplayed) { - const elementRect = this.generalMenuButtonRef.current.getBoundingClientRect(); + if (this.state.generalMenuPosition) { contextMenu = ( - +
          this.onTagRoom(e, DefaultTagID.Favourite)}> @@ -338,9 +342,8 @@ export default class RoomTile2 extends React.Component { {contextMenu} @@ -354,7 +357,7 @@ export default class RoomTile2 extends React.Component { const classes = classNames({ 'mx_RoomTile2': true, 'mx_RoomTile2_selected': this.state.selected, - 'mx_RoomTile2_hasMenuOpen': this.state.generalMenuDisplayed || this.state.notificationsMenuDisplayed, + 'mx_RoomTile2_hasMenuOpen': !!(this.state.generalMenuPosition || this.state.notificationsMenuPosition), 'mx_RoomTile2_minimized': this.props.isMinimized, }); @@ -416,6 +419,7 @@ export default class RoomTile2 extends React.Component { onMouseLeave={this.onTileMouseLeave} onClick={this.onTileClick} role="treeitem" + onContextMenu={this.onContextMenu} >
          From e8702aafa564030699e81a511aaeebcdd2785df4 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 1 Jul 2020 23:09:02 +0100 Subject: [PATCH 248/294] delint Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/RoomTile2.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index 47b5b4206b..7a07027913 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -73,7 +73,7 @@ interface IState { 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; - let top = elementRect.bottom + window.pageYOffset + 17; + const top = elementRect.bottom + window.pageYOffset + 17; const chevronFace = "none"; return {left, top, chevronFace}; }; From 6424ffb22a6e99ad61a094c4f8433b42c8d9f93b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 1 Jul 2020 23:13:54 +0100 Subject: [PATCH 249/294] fix repeated context menu interaction by not erroring Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/UserMenu.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 92a4666a9d..1cfe244845 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -125,9 +125,7 @@ export default class UserMenu extends React.Component { }); }; - private onCloseMenu = (ev: InputEvent) => { - ev.preventDefault(); - ev.stopPropagation(); + private onCloseMenu = () => { this.setState({contextMenuPosition: null}); }; From 4b27a67e336854f1f634b582ee3670a16a931623 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 1 Jul 2020 23:16:54 +0100 Subject: [PATCH 250/294] improve default behaviour for consistency Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/ContextMenu.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/components/structures/ContextMenu.js b/src/components/structures/ContextMenu.js index a56a987fcf..5ba2662796 100644 --- a/src/components/structures/ContextMenu.js +++ b/src/components/structures/ContextMenu.js @@ -347,10 +347,18 @@ export class ContextMenu extends React.Component { } // Semantic component for representing the AccessibleButton which launches a -export const ContextMenuButton = ({ label, isExpanded, children, ...props }) => { +export const ContextMenuButton = ({ label, isExpanded, children, onClick, onContextMenu, ...props }) => { const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); return ( - + { children } ); From 992349944a911a686e2992aecf4ea0df772d5a42 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 2 Jul 2020 12:18:48 +0100 Subject: [PATCH 251/294] Fix room list 2's room tile wrapping wrongly --- res/css/views/rooms/_RoomTile2.scss | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/res/css/views/rooms/_RoomTile2.scss b/res/css/views/rooms/_RoomTile2.scss index 44c5b6ee17..144a5ccf86 100644 --- a/res/css/views/rooms/_RoomTile2.scss +++ b/res/css/views/rooms/_RoomTile2.scss @@ -23,7 +23,6 @@ limitations under the License. // The tile is also a flexbox row itself display: flex; - flex-wrap: wrap; &.mx_RoomTile2_selected, &:hover, &.mx_RoomTile2_hasMenuOpen { background-color: $roomtile2-selected-bg-color; @@ -43,7 +42,8 @@ limitations under the License. .mx_RoomTile2_nameContainer { flex-grow: 1; - max-width: calc(100% - 58px); // 32px avatar, 18px badge area, 8px margin on avatar + min-width: 0; // allow flex to shrink it + margin-right: 8px; // spacing to buttons/badges // Create a new column layout flexbox for the name parts display: flex; @@ -81,8 +81,20 @@ limitations under the License. } } + //.mx_RoomTile2_badgeContainer, + .mx_RoomTile2_menuButton, + .mx_RoomTile2_notificationsButton { + width: 20px; + min-width: 20px; // yay flex + height: 20px; + margin: auto 0; + } + + .mx_RoomTile2_menuButton { + margin-left: 4px; // spacing between buttons + } + .mx_RoomTile2_badgeContainer { - width: 18px; height: 32px; // Create another flexbox row because it's super easy to position the badge at @@ -90,14 +102,15 @@ limitations under the License. display: flex; align-items: center; justify-content: center; + + .mx_NotificationBadge { + margin-right: 2px; + } } // The context menu buttons are hidden by default .mx_RoomTile2_menuButton, .mx_RoomTile2_notificationsButton { - width: 20px; - height: 20px; - margin: auto 0 auto 8px; position: relative; display: none; @@ -130,7 +143,7 @@ limitations under the License. .mx_RoomTile2_badgeContainer { width: 0; height: 0; - visibility: hidden; + display: none; } .mx_RoomTile2_notificationsButton, From c259408d71faab9b788174572b5a7bbf5f6e4d61 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 2 Jul 2020 12:35:06 +0100 Subject: [PATCH 252/294] fix alignment of dot and simplify CSS rules Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/views/rooms/_RoomTile2.scss | 31 +++++++++++++---------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/res/css/views/rooms/_RoomTile2.scss b/res/css/views/rooms/_RoomTile2.scss index 144a5ccf86..6241b3d0ba 100644 --- a/res/css/views/rooms/_RoomTile2.scss +++ b/res/css/views/rooms/_RoomTile2.scss @@ -81,36 +81,33 @@ limitations under the License. } } - //.mx_RoomTile2_badgeContainer, - .mx_RoomTile2_menuButton, - .mx_RoomTile2_notificationsButton { - width: 20px; - min-width: 20px; // yay flex - height: 20px; - margin: auto 0; - } - .mx_RoomTile2_menuButton { margin-left: 4px; // spacing between buttons } .mx_RoomTile2_badgeContainer { - height: 32px; - - // Create another flexbox row because it's super easy to position the badge at - // the end this way. - display: flex; - align-items: center; - justify-content: center; + height: 16px; + // don't set width so that it takes no space when there is no badge to show + margin: auto 0; // vertically align .mx_NotificationBadge { - margin-right: 2px; + margin-right: 2px; // centering + } + + .mx_NotificationBadge_dot { + // make the smaller dot occupy the same width for centering + margin-left: 5px; + margin-right: 7px; } } // The context menu buttons are hidden by default .mx_RoomTile2_menuButton, .mx_RoomTile2_notificationsButton { + width: 20px; + min-width: 20px; // yay flex + height: 20px; + margin: auto 0; position: relative; display: none; From b5c94acbe6d5541c3fd94c7b1622a48f3e22bbe6 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 2 Jul 2020 13:17:51 +0100 Subject: [PATCH 253/294] Remove unused crypto import --- src/components/structures/MatrixChat.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 79bdf743ce..9e3e112a28 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -23,7 +23,6 @@ import * as Matrix from "matrix-js-sdk"; import { InvalidStoreError } from "matrix-js-sdk/src/errors"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; -import { isCryptoAvailable } from 'matrix-js-sdk/src/crypto'; // focus-visible is a Polyfill for the :focus-visible CSS pseudo-attribute used by _AccessibleButton.scss import 'focus-visible'; // what-input helps improve keyboard accessibility From a928785f723356c839ce00d4974a654825945d7c Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 2 Jul 2020 13:19:27 +0100 Subject: [PATCH 254/294] Check whether crypto is enabled in room recovery reminder This avoids a soft crash that may occur otherwise. Fixes https://github.com/vector-im/riot-web/issues/14289 --- src/components/structures/RoomView.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 19f1cccebd..519c4c1f8e 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -1819,6 +1819,7 @@ export default createReactClass({ ); const showRoomRecoveryReminder = ( + this.context.isCryptoEnabled() && SettingsStore.getValue("showRoomRecoveryReminder") && this.context.isRoomEncrypted(this.state.room.roomId) && this.context.getKeyBackupEnabled() === false From 1c0d46b6e1a15caa5efd5084583cf2715c11055f Mon Sep 17 00:00:00 2001 From: Jorik Schellekens Date: Thu, 2 Jul 2020 15:26:51 +0100 Subject: [PATCH 255/294] Make breadcrumbs respsect setting --- src/stores/BreadcrumbsStore.ts | 5 +---- src/stores/room-list/RoomListStore2.ts | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/stores/BreadcrumbsStore.ts b/src/stores/BreadcrumbsStore.ts index 9905dd4345..c78f15c3b4 100644 --- a/src/stores/BreadcrumbsStore.ts +++ b/src/stores/BreadcrumbsStore.ts @@ -21,7 +21,6 @@ import { AsyncStoreWithClient } from "./AsyncStoreWithClient"; import defaultDispatcher from "../dispatcher/dispatcher"; import { arrayHasDiff } from "../utils/arrays"; import { RoomListStoreTempProxy } from "./room-list/RoomListStoreTempProxy"; -import _reduce from 'lodash/reduce'; const MAX_ROOMS = 20; // arbitrary const AUTOJOIN_WAIT_THRESHOLD_MS = 90000; // 90s, the time we wait for an autojoined room to show up @@ -52,9 +51,7 @@ export class BreadcrumbsStore extends AsyncStoreWithClient { } public get visible(): boolean { - // @ts-ignore - TypeScript really wants this to be [tagId: string] but we know better. - const roomCount = _reduce(RoomListStoreTempProxy.getRoomLists(), (result, rooms) => result + rooms.length, 0); - return roomCount >= 20; + return this.state.enabled && this.matrixClient.getVisibleRooms().length >= 20; } protected async onAction(payload: ActionPayload) { diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index b4d96becc4..73256e4de4 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -188,7 +188,7 @@ export class RoomListStore2 extends AsyncStore { const tryUpdate = async (updatedRoom: Room) => { // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 console.log(`[RoomListDebug] Live timeline event ${eventPayload.event.getId()}` + - ` in ${updatedRoom.roomId}`); + ` in ${updatedRoom.roomId}`); if (eventPayload.event.getType() === 'm.room.tombstone' && eventPayload.event.getStateKey() === '') { // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 console.log(`[RoomListDebug] Got tombstone event - trying to remove now-dead room`); From b7aa8203b683d51ceeb1017cc435955bf9679ddd Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 2 Jul 2020 09:04:38 -0600 Subject: [PATCH 256/294] Wedge community invites into the new room list Fixes https://github.com/vector-im/riot-web/issues/14179 Disclaimer: this is all of the horrible because it's not meant to be here. Invites in general are likely to move out of the room list, which means this is temporary. Additionally, the communities rework will take care of this more correctly. For now, we support the absolute bare minimum to have them shown. --- src/components/views/rooms/RoomList2.tsx | 40 ++++++ src/components/views/rooms/RoomSublist2.tsx | 19 ++- src/components/views/rooms/TemporaryTile.tsx | 114 ++++++++++++++++++ .../room-list/filters/NameFilterCondition.ts | 8 +- 4 files changed, 177 insertions(+), 4 deletions(-) create mode 100644 src/components/views/rooms/TemporaryTile.tsx diff --git a/src/components/views/rooms/RoomList2.tsx b/src/components/views/rooms/RoomList2.tsx index a1298e107b..606f2d60e9 100644 --- a/src/components/views/rooms/RoomList2.tsx +++ b/src/components/views/rooms/RoomList2.tsx @@ -25,10 +25,15 @@ import { ITagMap } from "../../../stores/room-list/algorithms/models"; import { DefaultTagID, TagID } from "../../../stores/room-list/models"; import { Dispatcher } from "flux"; import dis from "../../../dispatcher/dispatcher"; +import defaultDispatcher from "../../../dispatcher/dispatcher"; import RoomSublist2 from "./RoomSublist2"; import { ActionPayload } from "../../../dispatcher/payloads"; import { NameFilterCondition } from "../../../stores/room-list/filters/NameFilterCondition"; import { ListLayout } from "../../../stores/room-list/ListLayout"; +import { MatrixClientPeg } from "../../../MatrixClientPeg"; +import GroupAvatar from "../avatars/GroupAvatar"; +import TemporaryTile from "./TemporaryTile"; +import { NotificationColor, StaticNotificationState } from "./NotificationBadge"; // 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 @@ -173,6 +178,39 @@ export default class RoomList2 extends React.Component { }); } + private renderCommunityInvites(): React.ReactElement[] { + // TODO: Put community invites in a more sensible place (not in the room list) + return MatrixClientPeg.get().getGroups().filter(g => { + if (g.myMembership !== 'invite') return false; + return !this.searchFilter || this.searchFilter.matches(g.name); + }).map(g => { + const avatar = ( + + ); + const openGroup = () => { + defaultDispatcher.dispatch({ + action: 'view_group', + group_id: g.groupId, + }); + }; + return ( + + ); + }); + } + private renderSublists(): React.ReactElement[] { const components: React.ReactElement[] = []; @@ -195,6 +233,7 @@ export default class RoomList2 extends React.Component { if (!aesthetics) throw new Error(`Tag ${orderedTagId} does not have aesthetics`); const onAddRoomFn = aesthetics.onAddRoom ? () => aesthetics.onAddRoom(dis) : null; + const extraTiles = orderedTagId === DefaultTagID.Invite ? this.renderCommunityInvites() : null; components.push( { isInvite={aesthetics.isInvite} layout={this.state.layouts.get(orderedTagId)} isMinimized={this.props.isMinimized} + extraBadTilesThatShouldntExist={extraTiles} /> ); } diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index c64d62ebea..87796924e8 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -62,6 +62,10 @@ interface IProps { isMinimized: boolean; tagId: TagID; + // TODO: Don't use this. It's for community invites, and community invites shouldn't be here. + // You should feel bad if you use this. + extraBadTilesThatShouldntExist?: React.ReactElement[]; + // TODO: Account for https://github.com/vector-im/riot-web/issues/14179 } @@ -87,8 +91,7 @@ export default class RoomSublist2 extends React.Component { } private get numTiles(): number { - // TODO: Account for group invites: https://github.com/vector-im/riot-web/issues/14179 - return (this.props.rooms || []).length; + return (this.props.rooms || []).length + (this.props.extraBadTilesThatShouldntExist || []).length; } private get numVisibleTiles(): number { @@ -187,6 +190,10 @@ export default class RoomSublist2 extends React.Component { const tiles: React.ReactElement[] = []; + if (this.props.extraBadTilesThatShouldntExist) { + tiles.push(...this.props.extraBadTilesThatShouldntExist); + } + if (this.props.rooms) { const visibleRooms = this.props.rooms.slice(0, this.numVisibleTiles); for (const room of visibleRooms) { @@ -202,6 +209,14 @@ export default class RoomSublist2 extends React.Component { } } + // We only have to do this because of the extra tiles. We do it conditionally + // to avoid spending cycles on slicing. It's generally fine to do this though + // as users are unlikely to have more than a handful of tiles when the extra + // tiles are used. + if (tiles.length > this.numVisibleTiles) { + return tiles.slice(0, this.numVisibleTiles); + } + return tiles; } diff --git a/src/components/views/rooms/TemporaryTile.tsx b/src/components/views/rooms/TemporaryTile.tsx new file mode 100644 index 0000000000..676969cade --- /dev/null +++ b/src/components/views/rooms/TemporaryTile.tsx @@ -0,0 +1,114 @@ +/* +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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from "react"; +import classNames from "classnames"; +import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex"; +import AccessibleButton from "../../views/elements/AccessibleButton"; +import NotificationBadge, { INotificationState, NotificationColor } from "./NotificationBadge"; + +interface IProps { + isMinimized: boolean; + isSelected: boolean; + displayName: string; + avatar: React.ReactElement; + notificationState: INotificationState; + onClick: () => void; +} + +interface IState { + hover: boolean; +} + +export default class TemporaryTile extends React.Component { + constructor(props: IProps) { + super(props); + + this.state = { + hover: false, + }; + } + + private onTileMouseEnter = () => { + this.setState({hover: true}); + }; + + private onTileMouseLeave = () => { + this.setState({hover: false}); + }; + + public render(): React.ReactElement { + // XXX: We copy classes because it's easier + const classes = classNames({ + 'mx_RoomTile2': true, + 'mx_RoomTile2_selected': this.props.isSelected, + 'mx_RoomTile2_minimized': this.props.isMinimized, + }); + + const badge = ( + + ); + + let name = this.props.displayName; + if (typeof name !== 'string') name = ''; + name = name.replace(":", ":\u200b"); // add a zero-width space to allow linewrapping after the colon + + const nameClasses = classNames({ + "mx_RoomTile2_name": true, + "mx_RoomTile2_nameHasUnreadEvents": this.props.notificationState.color >= NotificationColor.Bold, + }); + + let nameContainer = ( +
          +
          + {name} +
          +
          + ); + if (this.props.isMinimized) nameContainer = null; + + const avatarSize = 32; + return ( + + + {({onFocus, isActive, ref}) => + +
          + {this.props.avatar} +
          + {nameContainer} +
          + {badge} +
          +
          + } +
          +
          + ); + } +} diff --git a/src/stores/room-list/filters/NameFilterCondition.ts b/src/stores/room-list/filters/NameFilterCondition.ts index 8625cd932c..12f147990d 100644 --- a/src/stores/room-list/filters/NameFilterCondition.ts +++ b/src/stores/room-list/filters/NameFilterCondition.ts @@ -60,11 +60,15 @@ export class NameFilterCondition extends EventEmitter implements IFilterConditio if (!room.name) return false; // should realistically not happen: the js-sdk always calculates a name + return this.matches(room.name); + } + + public matches(val: string): boolean { // Note: we have to match the filter with the removeHiddenChars() room name because the // function strips spaces and other characters (M becomes RN for example, in lowercase). // We also doubly convert to lowercase to work around oddities of the library. - const noSecretsFilter = removeHiddenChars(lcFilter).toLowerCase(); - const noSecretsName = removeHiddenChars(room.name.toLowerCase()).toLowerCase(); + const noSecretsFilter = removeHiddenChars(this.search.toLowerCase()).toLowerCase(); + const noSecretsName = removeHiddenChars(val.toLowerCase()).toLowerCase(); return noSecretsName.includes(noSecretsFilter); } } From 32642d592c9599f54853d3f850fb021eac3c40f2 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 2 Jul 2020 09:27:42 -0600 Subject: [PATCH 257/294] Add a key --- src/components/views/rooms/RoomList2.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/rooms/RoomList2.tsx b/src/components/views/rooms/RoomList2.tsx index 606f2d60e9..5d7b8ad2c6 100644 --- a/src/components/views/rooms/RoomList2.tsx +++ b/src/components/views/rooms/RoomList2.tsx @@ -206,6 +206,7 @@ export default class RoomList2 extends React.Component { avatar={avatar} notificationState={StaticNotificationState.forSymbol("!", NotificationColor.Red)} onClick={openGroup} + key={`temporaryGroupTile_${g.groupId}`} /> ); }); From a6586120785f345a31d4ce7ca3a6c383cfe3b6bf Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 2 Jul 2020 19:48:06 +0100 Subject: [PATCH 258/294] Add click-to-jump on badge in the room sublist header Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../views/rooms/NotificationBadge.tsx | 35 ++++++++++++++----- src/components/views/rooms/RoomSublist2.tsx | 33 ++++++++++++++++- 2 files changed, 59 insertions(+), 9 deletions(-) diff --git a/src/components/views/rooms/NotificationBadge.tsx b/src/components/views/rooms/NotificationBadge.tsx index 2111310555..31f1ea2021 100644 --- a/src/components/views/rooms/NotificationBadge.tsx +++ b/src/components/views/rooms/NotificationBadge.tsx @@ -29,6 +29,8 @@ import { IDestroyable } from "../../../utils/IDestroyable"; import SettingsStore from "../../../settings/SettingsStore"; import { DefaultTagID, TagID } from "../../../stores/room-list/models"; import { readReceiptChangeIsFor } from "../../../utils/read-receipts"; +import AccessibleButton from "../elements/AccessibleButton"; +import { XOR } from "../../../@types/common"; export const NOTIFICATION_STATE_UPDATE = "update"; @@ -62,11 +64,18 @@ interface IProps { roomId?: string; } +interface IClickableProps extends IProps, React.InputHTMLAttributes { + /** + * If specified will return an AccessibleButton instead of a div. + */ + onClick?(ev: React.MouseEvent); +} + interface IState { showCounts: boolean; // whether or not to show counts. Independent of props.forceCount } -export default class NotificationBadge extends React.PureComponent { +export default class NotificationBadge extends React.PureComponent, IState> { private countWatcherRef: string; constructor(props: IProps) { @@ -109,20 +118,22 @@ export default class NotificationBadge extends React.PureComponent= NotificationColor.Red; - const hasCount = this.props.notification.color >= NotificationColor.Grey; - const hasUnread = this.props.notification.color >= NotificationColor.Bold; + // Don't show a badge if we don't need to + if (notification.color <= NotificationColor.None) return null; + + const hasNotif = notification.color >= NotificationColor.Red; + const hasCount = notification.color >= NotificationColor.Grey; + const hasUnread = notification.color >= NotificationColor.Bold; const couldBeEmpty = (!this.state.showCounts || hasUnread) && !hasNotif; let isEmptyBadge = couldBeEmpty && (!this.state.showCounts || !hasCount); - if (this.props.forceCount) { + if (forceCount) { isEmptyBadge = false; if (!hasCount) return null; // Can't render a badge } - let symbol = this.props.notification.symbol || formatMinimalBadgeCount(this.props.notification.count); + let symbol = notification.symbol || formatMinimalBadgeCount(notification.count); if (isEmptyBadge) symbol = ""; const classes = classNames({ @@ -134,6 +145,14 @@ export default class NotificationBadge extends React.PureComponent 2, }); + if (onClick) { + return ( + + {symbol} + + ); + } + return (
          {symbol} diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index c64d62ebea..dfd6cdaefa 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -33,6 +33,7 @@ import StyledRadioButton from "../elements/StyledRadioButton"; 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"; // 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 @@ -160,6 +161,29 @@ export default class RoomSublist2 extends React.Component { this.forceUpdate(); // because the layout doesn't trigger a re-render }; + private onBadgeClick = (ev: React.MouseEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + + let room; + if (this.props.tagId === DefaultTagID.Invite) { + // switch to first room in sortedList as that'll be the top of the list for the user + room = this.props.rooms && this.props.rooms[0]; + } else { + room = this.props.rooms.find((r: Room) => { + const notifState = this.state.notificationState.getForRoom(r); + return notifState.count > 0 && notifState.color === this.state.notificationState.color; + }); + } + + if (room) { + dis.dispatch({ + action: 'view_room', + room_id: room.roomId, + }); + } + }; + private onHeaderClick = (ev: React.MouseEvent) => { let target = ev.target as HTMLDivElement; if (!target.classList.contains('mx_RoomSublist2_headerText')) { @@ -287,7 +311,14 @@ export default class RoomSublist2 extends React.Component { // TODO: Use onFocus: https://github.com/vector-im/riot-web/issues/14180 const tabIndex = isActive ? 0 : -1; - const badge = ; + const badge = ( + + ); let addRoomButton = null; if (!!this.props.onAddRoom) { From ae2a6ebc07a4f3ed02f673322a7d65bf277c08c7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 2 Jul 2020 19:56:41 +0100 Subject: [PATCH 259/294] improve comments Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/RoomSublist2.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index dfd6cdaefa..5584b8a521 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -167,9 +167,10 @@ export default class RoomSublist2 extends React.Component { let room; if (this.props.tagId === DefaultTagID.Invite) { - // switch to first room in sortedList as that'll be the top of the list for the user + // switch to first room as that'll be the top of the list for the user room = this.props.rooms && this.props.rooms[0]; } else { + // find the first room with a count of the same colour as the badge count room = this.props.rooms.find((r: Room) => { const notifState = this.state.notificationState.getForRoom(r); return notifState.count > 0 && notifState.color === this.state.notificationState.color; From b65972d44f6e21ad59b4639b12c0cce779060106 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 2 Jul 2020 13:23:20 -0600 Subject: [PATCH 260/294] Fix indentation --- src/stores/room-list/RoomListStore2.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index d0ef88949c..e5205f6051 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -187,7 +187,7 @@ export class RoomListStore2 extends AsyncStore { const tryUpdate = async (updatedRoom: Room) => { // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 console.log(`[RoomListDebug] Live timeline event ${eventPayload.event.getId()}` + - ` in ${updatedRoom.roomId}`); + ` in ${updatedRoom.roomId}`); if (eventPayload.event.getType() === 'm.room.tombstone' && eventPayload.event.getStateKey() === '') { // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035 console.log(`[RoomListDebug] Got tombstone event - trying to remove now-dead room`); From 45f4a2a980f553cf801f80953d63a03616d4a28f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 2 Jul 2020 13:28:06 -0600 Subject: [PATCH 261/294] Fix imports for NotificationStates --- src/components/views/avatars/DecoratedRoomAvatar.tsx | 6 ++++-- src/components/views/rooms/RoomList2.tsx | 3 ++- src/components/views/rooms/TemporaryTile.tsx | 4 +++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/views/avatars/DecoratedRoomAvatar.tsx b/src/components/views/avatars/DecoratedRoomAvatar.tsx index 1156c80313..e0ad3202b8 100644 --- a/src/components/views/avatars/DecoratedRoomAvatar.tsx +++ b/src/components/views/avatars/DecoratedRoomAvatar.tsx @@ -20,7 +20,9 @@ import { Room } from "matrix-js-sdk/src/models/room"; import { TagID } from '../../../stores/room-list/models'; import RoomAvatar from "./RoomAvatar"; import RoomTileIcon from "../rooms/RoomTileIcon"; -import NotificationBadge, { INotificationState, TagSpecificNotificationState } from '../rooms/NotificationBadge'; +import NotificationBadge from '../rooms/NotificationBadge'; +import { INotificationState } from "../../../stores/notifications/INotificationState"; +import { TagSpecificNotificationState } from "../../../stores/notifications/TagSpecificNotificationState"; interface IProps { room: Room; @@ -60,4 +62,4 @@ export default class DecoratedRoomAvatar extends React.PureComponent; } -} \ No newline at end of file +} diff --git a/src/components/views/rooms/RoomList2.tsx b/src/components/views/rooms/RoomList2.tsx index 5d7b8ad2c6..db7a095118 100644 --- a/src/components/views/rooms/RoomList2.tsx +++ b/src/components/views/rooms/RoomList2.tsx @@ -33,7 +33,8 @@ import { ListLayout } from "../../../stores/room-list/ListLayout"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import GroupAvatar from "../avatars/GroupAvatar"; import TemporaryTile from "./TemporaryTile"; -import { NotificationColor, StaticNotificationState } from "./NotificationBadge"; +import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState"; +import { NotificationColor } from "../../../stores/notifications/NotificationColor"; // 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 diff --git a/src/components/views/rooms/TemporaryTile.tsx b/src/components/views/rooms/TemporaryTile.tsx index 676969cade..3087e738f1 100644 --- a/src/components/views/rooms/TemporaryTile.tsx +++ b/src/components/views/rooms/TemporaryTile.tsx @@ -18,7 +18,9 @@ import React from "react"; import classNames from "classnames"; import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex"; import AccessibleButton from "../../views/elements/AccessibleButton"; -import NotificationBadge, { INotificationState, NotificationColor } from "./NotificationBadge"; +import { INotificationState } from "../../../stores/notifications/INotificationState"; +import NotificationBadge from "./NotificationBadge"; +import { NotificationColor } from "../../../stores/notifications/NotificationColor"; interface IProps { isMinimized: boolean; From 349c3f70909b14bb9d32d893399c519250d20e24 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 2 Jul 2020 13:33:06 -0600 Subject: [PATCH 262/294] Only show mute notification icon on rooms, not all notif icons --- src/components/views/rooms/RoomTile2.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index 2647a24412..3060be8423 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -284,9 +284,10 @@ export default class RoomTile2 extends React.Component { mx_RoomTile2_iconBell: state === ALL_MESSAGES_LOUD || state === ALL_MESSAGES, mx_RoomTile2_iconBellDot: state === MENTIONS_ONLY, mx_RoomTile2_iconBellCrossed: state === MUTE, - // XXX: RoomNotifs assumes ALL_MESSAGES is default, this is wrong, - // but cannot be fixed until FTUE Notifications lands. - mx_RoomTile2_notificationsButton_show: state !== ALL_MESSAGES, + + // Only show the icon by default if the room is overridden to muted. + // TODO: [FTUE Notifications] Probably need to detect global mute state + mx_RoomTile2_notificationsButton_show: state === MUTE, }); return ( From a5001e50aa45439aec777b5d92e8590d70ea036d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 2 Jul 2020 13:33:24 -0600 Subject: [PATCH 263/294] Disable all unread decorations on muted rooms --- src/stores/notifications/RoomNotificationState.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/stores/notifications/RoomNotificationState.ts b/src/stores/notifications/RoomNotificationState.ts index b9bc3f3492..a73c503453 100644 --- a/src/stores/notifications/RoomNotificationState.ts +++ b/src/stores/notifications/RoomNotificationState.ts @@ -81,7 +81,12 @@ export class RoomNotificationState extends EventEmitter implements IDestroyable, private updateNotificationState() { const before = {count: this.count, symbol: this.symbol, color: this.color}; - if (this.roomIsInvite) { + if (RoomNotifs.getRoomNotifsState(this.room.roomId) === RoomNotifs.MUTE) { + // When muted we suppress all notification states, even if we have context on them. + this._color = NotificationColor.None; + this._symbol = null; + this._count = 0; + } else if (this.roomIsInvite) { this._color = NotificationColor.Red; this._symbol = "!"; this._count = 1; // not used, technically From 7ea3164507354c950082832afddf9c6f826808b6 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 2 Jul 2020 13:39:20 -0600 Subject: [PATCH 264/294] Fix alignment of dot badges in new room list --- res/css/views/rooms/_RoomTile2.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/res/css/views/rooms/_RoomTile2.scss b/res/css/views/rooms/_RoomTile2.scss index b11eb47d1c..3d2f791c01 100644 --- a/res/css/views/rooms/_RoomTile2.scss +++ b/res/css/views/rooms/_RoomTile2.scss @@ -83,6 +83,10 @@ limitations under the License. // don't set width so that it takes no space when there is no badge to show margin: auto 0; // vertically align + // Create a flexbox to make aligning dot badges easier + display: flex; + align-items: center; + .mx_NotificationBadge { margin-right: 2px; // centering } From c3ad8548683601b634dd40be797b82d1ae2d30cc Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 2 Jul 2020 13:50:25 -0600 Subject: [PATCH 265/294] Fix alignment of avatars on community invites --- res/css/views/rooms/_RoomTile2.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/rooms/_RoomTile2.scss b/res/css/views/rooms/_RoomTile2.scss index b11eb47d1c..38c30cf320 100644 --- a/res/css/views/rooms/_RoomTile2.scss +++ b/res/css/views/rooms/_RoomTile2.scss @@ -29,7 +29,7 @@ limitations under the License. border-radius: 32px; } - .mx_DecoratedRoomAvatar { + .mx_DecoratedRoomAvatar, .mx_RoomTile2_avatarContainer { margin-right: 8px; } From e51f9d24920f4f1a2af0777c6ee7de7d2cfaba25 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 2 Jul 2020 13:53:38 -0600 Subject: [PATCH 266/294] Fix closing the context menu causing the tile to be selected Fixes https://github.com/vector-im/riot-web/issues/14293 --- src/components/views/rooms/RoomTile2.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index 2647a24412..920137ba88 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -17,7 +17,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { createRef } from "react"; +import React from "react"; import { Room } from "matrix-js-sdk/src/models/room"; import classNames from "classnames"; import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex"; @@ -30,7 +30,6 @@ 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 RoomTileIcon from "./RoomTileIcon"; import { getRoomNotifsState, ALL_MESSAGES, ALL_MESSAGES_LOUD, MENTIONS_ONLY, MUTE } from "../../../RoomNotifs"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { setRoomNotifsState } from "../../../RoomNotifs"; @@ -157,7 +156,9 @@ export default class RoomTile2 extends React.Component { this.setState({notificationsMenuPosition: target.getBoundingClientRect()}); }; - private onCloseNotificationsMenu = () => { + private onCloseNotificationsMenu = (ev: InputEvent) => { + ev.preventDefault(); + ev.stopPropagation(); this.setState({notificationsMenuPosition: null}); }; @@ -179,7 +180,9 @@ export default class RoomTile2 extends React.Component { }); }; - private onCloseGeneralMenu = () => { + private onCloseGeneralMenu = (ev: InputEvent) => { + ev.preventDefault(); + ev.stopPropagation(); this.setState({generalMenuPosition: null}); }; From aa702514ce2be1d124866e70553fb2b616d7217e Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 2 Jul 2020 13:59:28 -0600 Subject: [PATCH 267/294] Don't try and show context menus if we don't have one Fixes https://github.com/vector-im/riot-web/issues/14295 --- src/components/views/rooms/RoomTile2.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index 920137ba88..7dbe9f1f10 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -119,6 +119,10 @@ export default class RoomTile2 extends React.Component { ActiveRoomObserver.addListener(this.props.room.roomId, this.onActiveRoomUpdate); } + private get showContextMenu(): boolean { + return !this.props.isMinimized && this.props.tag !== DefaultTagID.Invite; + } + public componentWillUnmount() { if (this.props.room) { ActiveRoomObserver.removeListener(this.props.room.roomId, this.onActiveRoomUpdate); @@ -170,6 +174,9 @@ export default class RoomTile2 extends React.Component { }; private onContextMenu = (ev: React.MouseEvent) => { + // If we don't have a context menu to show, ignore the action. + if (!this.showContextMenu) return; + ev.preventDefault(); ev.stopPropagation(); this.setState({ @@ -239,7 +246,7 @@ export default class RoomTile2 extends React.Component { private onClickMute = ev => this.saveNotifState(ev, MUTE); private renderNotificationsMenu(): React.ReactElement { - if (this.props.isMinimized || MatrixClientPeg.get().isGuest() || this.props.tag === DefaultTagID.Invite) { + if (MatrixClientPeg.get().isGuest() || !this.showContextMenu) { // the menu makes no sense in these cases so do not show one return null; } @@ -306,12 +313,9 @@ export default class RoomTile2 extends React.Component { } private renderGeneralMenu(): React.ReactElement { - if (this.props.isMinimized) return null; // no menu when minimized + if (!this.showContextMenu) return null; // no menu to show - // TODO: Get a proper invite context menu, or take invites out of the room list. - if (this.props.tag === DefaultTagID.Invite) { - return null; - } + // TODO: We could do with a proper invite context menu, unlike what showContextMenu suggests let contextMenu = null; if (this.state.generalMenuPosition) { From e08512020f93cd78701ebc310e6ea9963e9809ad Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 2 Jul 2020 14:05:31 -0600 Subject: [PATCH 268/294] Fix a couple badge alignment issues with community invites --- res/css/views/avatars/_DecoratedRoomAvatar.scss | 3 ++- res/css/views/rooms/_RoomTile2.scss | 2 +- src/components/views/rooms/TemporaryTile.tsx | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/res/css/views/avatars/_DecoratedRoomAvatar.scss b/res/css/views/avatars/_DecoratedRoomAvatar.scss index 984fa0ce9a..b500d44a43 100644 --- a/res/css/views/avatars/_DecoratedRoomAvatar.scss +++ b/res/css/views/avatars/_DecoratedRoomAvatar.scss @@ -14,7 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_DecoratedRoomAvatar { +// XXX: We shouldn't be using TemporaryTile anywhere - delete it. +.mx_DecoratedRoomAvatar, .mx_TemporaryTile { position: relative; .mx_RoomTileIcon { diff --git a/res/css/views/rooms/_RoomTile2.scss b/res/css/views/rooms/_RoomTile2.scss index 38c30cf320..d366e1b226 100644 --- a/res/css/views/rooms/_RoomTile2.scss +++ b/res/css/views/rooms/_RoomTile2.scss @@ -148,7 +148,7 @@ limitations under the License. align-items: center; position: relative; - .mx_DecoratedRoomAvatar { + .mx_DecoratedRoomAvatar, .mx_RoomTile2_avatarContainer { margin-right: 0; } } diff --git a/src/components/views/rooms/TemporaryTile.tsx b/src/components/views/rooms/TemporaryTile.tsx index 3087e738f1..b6c165ecda 100644 --- a/src/components/views/rooms/TemporaryTile.tsx +++ b/src/components/views/rooms/TemporaryTile.tsx @@ -56,6 +56,7 @@ export default class TemporaryTile extends React.Component { // XXX: We copy classes because it's easier const classes = classNames({ 'mx_RoomTile2': true, + 'mx_TemporaryTile': true, 'mx_RoomTile2_selected': this.props.isSelected, 'mx_RoomTile2_minimized': this.props.isMinimized, }); @@ -85,7 +86,6 @@ export default class TemporaryTile extends React.Component { ); if (this.props.isMinimized) nameContainer = null; - const avatarSize = 32; return ( From 0d9ce0721f7777758482773ac2593f4444c64fe1 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 2 Jul 2020 14:11:31 -0600 Subject: [PATCH 269/294] Don't include empty badge container in minimized view Fixes https://github.com/vector-im/riot-web/issues/14294 It takes up space, and it won't hold anything anyways. --- src/components/views/rooms/RoomTile2.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index 2647a24412..92c69e54e7 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -369,11 +369,15 @@ export default class RoomTile2 extends React.Component { let badge: React.ReactNode; if (!this.props.isMinimized) { - badge = ; + badge = ( +
          + +
          + ); } // TODO: the original RoomTile uses state for the room name. Do we need to? @@ -429,9 +433,7 @@ export default class RoomTile2 extends React.Component { > {roomAvatar} {nameContainer} -
          - {badge} -
          + {badge} {this.renderNotificationsMenu()} {this.renderGeneralMenu()} From 1b782ce5f27365871103aacb2ee7058fe7ad43ac Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 2 Jul 2020 14:23:56 -0600 Subject: [PATCH 270/294] Enable the new room list by default and trigger an initial render We have to trigger an initial render because during the login process the user will have started syncing (causing lists to generate) but the RoomList component won't be mounted & listening and therefore won't receive the initial lists. By generating them on mount, we ensure that the lists are present once the user gets through the login process. --- src/components/views/rooms/RoomList2.tsx | 29 +++++++++++++++--------- src/settings/Settings.js | 2 +- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/components/views/rooms/RoomList2.tsx b/src/components/views/rooms/RoomList2.tsx index db7a095118..b0bb70c9a0 100644 --- a/src/components/views/rooms/RoomList2.tsx +++ b/src/components/views/rooms/RoomList2.tsx @@ -166,19 +166,26 @@ export default class RoomList2 extends React.Component { } public componentDidMount(): void { - RoomListStore.instance.on(LISTS_UPDATE_EVENT, (store: RoomListStore2) => { - const newLists = store.orderedLists; - console.log("new lists", newLists); - - const layoutMap = new Map(); - for (const tagId of Object.keys(newLists)) { - layoutMap.set(tagId, new ListLayout(tagId)); - } - - this.setState({sublists: newLists, layouts: layoutMap}); - }); + RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.updateLists); + this.updateLists(); // trigger the first update } + public componentWillUnmount() { + RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.updateLists); + } + + private updateLists = () => { + const newLists = RoomListStore.instance.orderedLists; + console.log("new lists", newLists); + + const layoutMap = new Map(); + for (const tagId of Object.keys(newLists)) { + layoutMap.set(tagId, new ListLayout(tagId)); + } + + this.setState({sublists: newLists, layouts: layoutMap}); + }; + private renderCommunityInvites(): React.ReactElement[] { // TODO: Put community invites in a more sensible place (not in the room list) return MatrixClientPeg.get().getGroups().filter(g => { diff --git a/src/settings/Settings.js b/src/settings/Settings.js index cc45bbb4c7..218e151ef4 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -150,7 +150,7 @@ export const SETTINGS = { isFeature: true, displayName: _td("Use the improved room list (will refresh to apply changes)"), supportedLevels: LEVELS_FEATURE, - default: false, + default: true, controller: new ReloadOnChangeController(), }, "feature_custom_themes": { From 547690374eade938bf4044e2a092d3571c6d7ce2 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 2 Jul 2020 14:53:21 -0600 Subject: [PATCH 271/294] Wrap event stoppage in null checks Some of the code paths (particularly onFinished) do not have events, but the code paths we care about to prevent the room selection do have events - we can stop those without stopping further menus. --- src/components/views/rooms/RoomTile2.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index 7dbe9f1f10..6f686dbac3 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -160,9 +160,11 @@ export default class RoomTile2 extends React.Component { this.setState({notificationsMenuPosition: target.getBoundingClientRect()}); }; - private onCloseNotificationsMenu = (ev: InputEvent) => { - ev.preventDefault(); - ev.stopPropagation(); + private onCloseNotificationsMenu = (ev?: InputEvent) => { + if (ev) { + ev.preventDefault(); + ev.stopPropagation(); + } this.setState({notificationsMenuPosition: null}); }; @@ -187,9 +189,11 @@ export default class RoomTile2 extends React.Component { }); }; - private onCloseGeneralMenu = (ev: InputEvent) => { - ev.preventDefault(); - ev.stopPropagation(); + private onCloseGeneralMenu = (ev?: InputEvent) => { + if (ev) { + ev.preventDefault(); + ev.stopPropagation(); + } this.setState({generalMenuPosition: null}); }; From a6e0799b57c4830a61d09dc749510722691de973 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 2 Jul 2020 15:05:01 -0600 Subject: [PATCH 272/294] Handle push rule changes in the RoomNotificationState --- src/stores/notifications/RoomNotificationState.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/stores/notifications/RoomNotificationState.ts b/src/stores/notifications/RoomNotificationState.ts index a73c503453..f9b19fcbcb 100644 --- a/src/stores/notifications/RoomNotificationState.ts +++ b/src/stores/notifications/RoomNotificationState.ts @@ -37,6 +37,7 @@ export class RoomNotificationState extends EventEmitter implements IDestroyable, this.room.on("Room.timeline", this.handleRoomEventUpdate); this.room.on("Room.redaction", this.handleRoomEventUpdate); MatrixClientPeg.get().on("Event.decrypted", this.handleRoomEventUpdate); + MatrixClientPeg.get().on("accountData", this.handleAccountDataUpdate); this.updateNotificationState(); } @@ -62,6 +63,7 @@ export class RoomNotificationState extends EventEmitter implements IDestroyable, this.room.removeListener("Room.redaction", this.handleRoomEventUpdate); if (MatrixClientPeg.get()) { MatrixClientPeg.get().removeListener("Event.decrypted", this.handleRoomEventUpdate); + MatrixClientPeg.get().removeListener("accountData", this.handleAccountDataUpdate); } } @@ -78,6 +80,12 @@ export class RoomNotificationState extends EventEmitter implements IDestroyable, this.updateNotificationState(); }; + private handleAccountDataUpdate = (ev: MatrixEvent) => { + if (ev.getType() === "m.push_rules") { + this.updateNotificationState(); + } + }; + private updateNotificationState() { const before = {count: this.count, symbol: this.symbol, color: this.color}; From 3847dc91c06f8796ddfb2aa97440ccc40cc7b165 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 2 Jul 2020 15:15:33 -0600 Subject: [PATCH 273/294] Move the stoppage to somewhere more generic --- src/components/structures/ContextMenu.js | 9 ++++++++- src/components/views/rooms/RoomTile2.tsx | 12 ++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/components/structures/ContextMenu.js b/src/components/structures/ContextMenu.js index 5ba2662796..e43b0d1431 100644 --- a/src/components/structures/ContextMenu.js +++ b/src/components/structures/ContextMenu.js @@ -140,6 +140,13 @@ export class ContextMenu extends React.Component { e.stopPropagation(); }; + // Prevent clicks on the background from going through to the component which opened the menu. + _onFinished = (ev: InputEvent) => { + ev.stopPropagation(); + ev.preventDefault(); + if (this.props.onFinished) this.props.onFinished(); + }; + _onMoveFocus = (element, up) => { let descending = false; // are we currently descending or ascending through the DOM tree? @@ -326,7 +333,7 @@ export class ContextMenu extends React.Component { let background; if (hasBackground) { background = ( -
          +
          ); } diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index 6f686dbac3..1024198560 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -160,11 +160,7 @@ export default class RoomTile2 extends React.Component { this.setState({notificationsMenuPosition: target.getBoundingClientRect()}); }; - private onCloseNotificationsMenu = (ev?: InputEvent) => { - if (ev) { - ev.preventDefault(); - ev.stopPropagation(); - } + private onCloseNotificationsMenu = () => { this.setState({notificationsMenuPosition: null}); }; @@ -189,11 +185,7 @@ export default class RoomTile2 extends React.Component { }); }; - private onCloseGeneralMenu = (ev?: InputEvent) => { - if (ev) { - ev.preventDefault(); - ev.stopPropagation(); - } + private onCloseGeneralMenu = () => { this.setState({generalMenuPosition: null}); }; 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 274/294] 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 5c5482a8ae530a9d06ca4fe356d9a57a8f0efc22 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 2 Jul 2020 23:20:16 +0100 Subject: [PATCH 275/294] I've got 99 problems and this badge mismatch is no longer one Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/RoomTile2.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index 577387948c..25b60b7898 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -287,8 +287,9 @@ export default class RoomTile2 extends React.Component { const classes = classNames("mx_RoomTile2_notificationsButton", { // Show bell icon for the default case too. - mx_RoomTile2_iconBell: state === ALL_MESSAGES_LOUD || state === ALL_MESSAGES, - mx_RoomTile2_iconBellDot: state === MENTIONS_ONLY, + mx_RoomTile2_iconBell: state === state === ALL_MESSAGES, + mx_RoomTile2_iconBellDot: state === ALL_MESSAGES_LOUD, + mx_RoomTile2_iconBellMentions: state === MENTIONS_ONLY, mx_RoomTile2_iconBellCrossed: state === MUTE, // Only show the icon by default if the room is overridden to muted. From ae076a7439a854104db2f1e2fe555e6ad0941529 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 2 Jul 2020 16:23:33 -0600 Subject: [PATCH 276/294] Add a null guard for message event previews --- src/stores/room-list/previews/MessageEventPreview.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/stores/room-list/previews/MessageEventPreview.ts b/src/stores/room-list/previews/MessageEventPreview.ts index 6f0dc14a58..86ec4c539b 100644 --- a/src/stores/room-list/previews/MessageEventPreview.ts +++ b/src/stores/room-list/previews/MessageEventPreview.ts @@ -30,6 +30,8 @@ export class MessageEventPreview implements IPreview { eventContent = event.getContent()['m.new_content']; } + if (!eventContent || !eventContent['body']) return null; // invalid for our purposes + let body = (eventContent['body'] || '').trim(); const msgtype = eventContent['msgtype']; if (!body || !msgtype) return null; // invalid event, no preview From 98ce1dafee5e009844a0d0bc3bbb82c08ad5a46a Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 2 Jul 2020 16:36:07 -0600 Subject: [PATCH 277/294] Remove a bunch of noisy logging from the room list None of these logs are actually needed for troubleshooting anymore. --- 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 deletions(-) diff --git a/src/stores/room-list/RoomListStore2.ts b/src/stores/room-list/RoomListStore2.ts index e5205f6051..d4aec93035 100644 --- a/src/stores/room-list/RoomListStore2.ts +++ b/src/stores/room-list/RoomListStore2.ts @@ -101,8 +101,6 @@ 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; } } @@ -299,8 +297,6 @@ 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); } } @@ -367,8 +363,6 @@ 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); }; @@ -408,8 +402,6 @@ 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); @@ -417,8 +409,6 @@ 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 36abf86975..80ca4656af 100644 --- a/src/stores/room-list/algorithms/Algorithm.ts +++ b/src/stores/room-list/algorithms/Algorithm.ts @@ -272,9 +272,6 @@ 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; }, []); @@ -310,9 +307,6 @@ 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) { @@ -351,8 +345,6 @@ 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 @@ -363,8 +355,6 @@ 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 } @@ -373,8 +363,6 @@ 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); } @@ -466,13 +454,9 @@ 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); } @@ -483,11 +467,7 @@ 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; } @@ -497,9 +477,6 @@ 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 e95f92f985..71b6e89df3 100644 --- a/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts +++ b/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts @@ -87,9 +87,6 @@ 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 f74329cb4d..1a75d8cf06 100644 --- a/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts +++ b/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts @@ -28,9 +28,6 @@ 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 9f7d8daaa3..45e65fb4f4 100644 --- a/src/stores/room-list/filters/CommunityFilterCondition.ts +++ b/src/stores/room-list/filters/CommunityFilterCondition.ts @@ -52,8 +52,6 @@ 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 12f147990d..6014a122f8 100644 --- a/src/stores/room-list/filters/NameFilterCondition.ts +++ b/src/stores/room-list/filters/NameFilterCondition.ts @@ -41,8 +41,6 @@ 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 22ac7e6def07a29812b2951dc7cbd6b6df6cb819 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 3 Jul 2020 00:14:51 +0100 Subject: [PATCH 278/294] fix notifications icons some more Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/RoomTile2.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index 25b60b7898..2c5b46f63c 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -287,7 +287,7 @@ export default class RoomTile2 extends React.Component { const classes = classNames("mx_RoomTile2_notificationsButton", { // Show bell icon for the default case too. - mx_RoomTile2_iconBell: state === state === ALL_MESSAGES, + mx_RoomTile2_iconBell: state === ALL_MESSAGES, mx_RoomTile2_iconBellDot: state === ALL_MESSAGES_LOUD, mx_RoomTile2_iconBellMentions: state === MENTIONS_ONLY, mx_RoomTile2_iconBellCrossed: state === MUTE, From 04142a8723d2fb07983501ad52dc7b559b087ccf Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 3 Jul 2020 12:06:00 +0100 Subject: [PATCH 279/294] 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 280/294] 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 281/294] 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 282/294] 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 283/294] 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 284/294] 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 285/294] 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 286/294] 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 287/294] 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 288/294] 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 289/294] 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 290/294] 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 291/294] 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 292/294] 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 293/294] 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 294/294] 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;