From 2e7bd2e3f03da46f171846d9d81466fb414f65d9 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Mon, 20 Apr 2020 23:05:50 +0300 Subject: [PATCH 001/286] Use flexboxes in AppTile/AppDrawer CSS for automatic resizing Using flexboxes removes the need for pixel constants, and the app content now resizes to fill the tiles. Signed-off-by: Pauli Virtanen --- res/css/views/rooms/_AppsDrawer.scss | 55 ++++++++++++++++-------- src/components/views/elements/AppTile.js | 12 +++--- 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/res/css/views/rooms/_AppsDrawer.scss b/res/css/views/rooms/_AppsDrawer.scss index 1b1bab67bc..61da3a360b 100644 --- a/res/css/views/rooms/_AppsDrawer.scss +++ b/res/css/views/rooms/_AppsDrawer.scss @@ -16,17 +16,14 @@ limitations under the License. */ /* -the tile title bar is 5 (top border) + 12 (title, buttons) + 5 (bottom padding) px = 22px -the body is assumed to be 300px (assumed by at least the sticker pickerm, perhaps elsewhere), -so the body height would be 300px - 22px (room for title bar) = 278px -BUT! the sticker picker also assumes it's a little less high than that because the iframe -for the sticker picker doesn't have any padding or margin on it's bottom. -so subtracking another 5px, which brings us at 273px. + Minimum size for usual AppTiles and fixed size for mini-tiles. */ -$AppsDrawerBodyHeight: 273px; +$AppTileMinHeight: 300px; +$MiniAppTileHeight: 114px; .mx_AppsDrawer { margin: 5px; + display: block; } .mx_AppsDrawer_hidden { @@ -36,7 +33,7 @@ $AppsDrawerBodyHeight: 273px; .mx_AppsContainer { display: flex; flex-direction: row; - align-items: center; + align-items: stretch; justify-content: center; } @@ -44,7 +41,7 @@ $AppsDrawerBodyHeight: 273px; order: 2; cursor: pointer; padding: 0; - margin: 5px auto 5px auto; + margin: 5px auto 5px 0px; color: $accent-color; font-size: $font-12px; } @@ -68,6 +65,9 @@ $AppsDrawerBodyHeight: 273px; margin-right: 5px; border: 5px solid $widget-menu-bar-bg-color; border-radius: 4px; + display: flex; + flex-direction: column; + min-height: $AppTileMinHeight; } .mx_AppTile:last-child { @@ -77,27 +77,40 @@ $AppsDrawerBodyHeight: 273px; .mx_AppTileFullWidth { max-width: 960px; width: 100%; - height: 100%; margin: 0; padding: 0; border: 5px solid $widget-menu-bar-bg-color; border-radius: 4px; + display: flex; + flex-direction: column; + min-height: $AppTileMinHeight; } .mx_AppTile_mini { max-width: 960px; width: 100%; - height: 100%; margin: 0; padding: 0; + display: flex; + flex-direction: column; + height: $MiniAppTileHeight; } -.mx_AppTile_persistedWrapper { - height: $AppsDrawerBodyHeight; +.mx_AppTile.mx_AppTile_minimised, +.mx_AppTileFullWidth.mx_AppTile_minimised, +.mx_AppTile_mini.mx_AppTile_minimised { + min-height: inherit; } +.mx_AppTile .mx_AppTile_persistedWrapper, +.mx_AppTileFullWidth .mx_AppTile_persistedWrapper, .mx_AppTile_mini .mx_AppTile_persistedWrapper { - height: 114px; + flex: 1; +} + +.mx_AppTile_persistedWrapper div { + width: 100%; + height: 100%; } .mx_AppTileMenuBar { @@ -109,6 +122,7 @@ $AppsDrawerBodyHeight: 273px; align-items: center; justify-content: space-between; cursor: pointer; + width: 100%; } .mx_AppTileMenuBar_expanded { @@ -171,7 +185,7 @@ $AppsDrawerBodyHeight: 273px; } .mx_AppTileBody { - height: $AppsDrawerBodyHeight; + height: 100%; width: 100%; overflow: hidden; } @@ -182,6 +196,13 @@ $AppsDrawerBodyHeight: 273px; overflow: hidden; } +.mx_AppTile .mx_AppTileBody, +.mx_AppTileFullWidth .mx_AppTileBody, +.mx_AppTile_mini .mx_AppTileBody_mini { + height: inherit; + flex: 1; +} + .mx_AppTileBody_mini iframe { border: none; width: 100%; @@ -190,7 +211,7 @@ $AppsDrawerBodyHeight: 273px; .mx_AppTileBody iframe { width: 100%; - height: $AppsDrawerBodyHeight; + height: 100%; overflow: hidden; border: none; padding: 0; @@ -330,7 +351,7 @@ form.mx_Custom_Widget_Form div { align-items: center; font-weight: bold; position: relative; - height: $AppsDrawerBodyHeight; + height: 100%; } .mx_AppLoading .mx_Spinner { diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 6a5dfc97e0..61d36723d3 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -775,14 +775,16 @@ export default class AppTile extends React.Component { const showMinimiseButton = this.props.showMinimise && this.props.show; const showMaximiseButton = this.props.showMinimise && !this.props.show; - let appTileClass; + let appTileClasses; if (this.props.miniMode) { - appTileClass = 'mx_AppTile_mini'; + appTileClasses = {mx_AppTile_mini: true}; } else if (this.props.fullWidth) { - appTileClass = 'mx_AppTileFullWidth'; + appTileClasses = {mx_AppTileFullWidth: true}; } else { - appTileClass = 'mx_AppTile'; + appTileClasses = {mx_AppTile: true}; } + appTileClasses.mx_AppTile_minimised = !this.props.show; + appTileClasses = classNames(appTileClasses); const menuBarClasses = classNames({ mx_AppTileMenuBar: true, @@ -814,7 +816,7 @@ export default class AppTile extends React.Component { } return -
+
{ this.props.showMenubar &&
From 11438aeee61cf3faabe170da49150003cd35ae52 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Tue, 21 Apr 2020 00:41:58 +0300 Subject: [PATCH 002/286] Fix resizer/sizer.js mouse event offset calculation The event coordinates are document coordinates, so the offset they are compared to should also be the document one. Signed-off-by: Pauli Virtanen --- src/resizer/sizer.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/resizer/sizer.js b/src/resizer/sizer.js index 50861d34d5..4ce9232457 100644 --- a/src/resizer/sizer.js +++ b/src/resizer/sizer.js @@ -56,6 +56,18 @@ export default class Sizer { return this.vertical ? this.container.offsetTop : this.container.offsetLeft; } + /** @return {number} container offset to document */ + _getPageOffset() { + let element = this.container; + let offset = 0; + while (element) { + const pos = this.vertical ? element.offsetTop : element.offsetLeft; + offset = offset + pos; + element = element.offsetParent; + } + return offset; + } + setItemSize(item, size) { if (this.vertical) { item.style.height = `${Math.round(size)}px`; @@ -80,9 +92,9 @@ export default class Sizer { offsetFromEvent(event) { const pos = this.vertical ? event.pageY : event.pageX; if (this.reverse) { - return (this._getOffset() + this.getTotalSize()) - pos; + return (this._getPageOffset() + this.getTotalSize()) - pos; } else { - return pos - this._getOffset(); + return pos - this._getPageOffset(); } } } From e897e97fd6e15b39aec89080d0096d570dcdfd63 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Thu, 23 Apr 2020 22:52:28 +0300 Subject: [PATCH 003/286] Make AppsDrawer resizable by dragging its bottom border Signed-off-by: Pauli Virtanen --- res/css/views/rooms/_AppsDrawer.scss | 38 ++++++++++++++++-- src/components/views/rooms/AppsDrawer.js | 51 +++++++++++++++++++++++- 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/res/css/views/rooms/_AppsDrawer.scss b/res/css/views/rooms/_AppsDrawer.scss index 61da3a360b..3a33b73ec9 100644 --- a/res/css/views/rooms/_AppsDrawer.scss +++ b/res/css/views/rooms/_AppsDrawer.scss @@ -16,9 +16,10 @@ limitations under the License. */ /* - Minimum size for usual AppTiles and fixed size for mini-tiles. + Size settings */ -$AppTileMinHeight: 300px; +$AppsDrawerMinHeight: 50px; +$AppsDrawerDefaultHeight: 300px; $MiniAppTileHeight: 114px; .mx_AppsDrawer { @@ -35,6 +36,13 @@ $MiniAppTileHeight: 114px; flex-direction: row; align-items: stretch; justify-content: center; + min-height: $AppsDrawerMinHeight; + height: $AppsDrawerDefaultHeight; +} + +.mx_AppsDrawer_minimised .mx_AppsContainer { + min-height: inherit; + height: inherit; } .mx_AddWidget_button { @@ -67,7 +75,6 @@ $MiniAppTileHeight: 114px; border-radius: 4px; display: flex; flex-direction: column; - min-height: $AppTileMinHeight; } .mx_AppTile:last-child { @@ -83,7 +90,6 @@ $MiniAppTileHeight: 114px; border-radius: 4px; display: flex; flex-direction: column; - min-height: $AppTileMinHeight; } .mx_AppTile_mini { @@ -378,3 +384,27 @@ form.mx_Custom_Widget_Form div { .mx_AppLoading iframe { display: none; } + +/* Hidden resize handle (Apptile bottom serves as indicator) */ +.mx_AppsDrawer .mx_ResizeHandle > div { + background: inherit; +} + +.mx_AppsDrawer_fullWidth .mx_ResizeHandle { + max-width: 960px; + margin-left: auto; + margin-right: auto; +} + +.mx_AppsDrawer_minimised .mx_ResizeHandle { + display: none; +} + +/* Avoid apptile iframes capturing mouse event focus when resizing */ +.mx_AppsDrawer_resizing iframe { + pointer-events: none; +} + +.mx_AppsDrawer_resizing .mx_AppTile_persistedWrapper { + z-index: 1; +} diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index b64eb33435..842b93170f 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -30,6 +30,9 @@ import WidgetEchoStore from "../../../stores/WidgetEchoStore"; import AccessibleButton from '../elements/AccessibleButton'; import {IntegrationManagers} from "../../../integrations/IntegrationManagers"; import SettingsStore from "../../../settings/SettingsStore"; +import classNames from 'classnames'; +import ResizeHandle from '../elements/ResizeHandle'; +import {Resizer, FixedDistributor} from '../../../resizer'; // The maximum number of widgets that can be added in a room const MAX_WIDGETS = 2; @@ -60,6 +63,7 @@ export default createReactClass({ MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents); WidgetEchoStore.on('update', this._updateApps); this.dispatcherRef = dis.register(this.onAction); + this._createResizer(); }, componentWillUnmount: function() { @@ -69,6 +73,10 @@ export default createReactClass({ } WidgetEchoStore.removeListener('update', this._updateApps); if (this.dispatcherRef) dis.unregister(this.dispatcherRef); + if (this.resizer) { + this.resizer.detach(); + this.resizer = null; + } }, // TODO: [REACT-WARNING] Replace with appropriate lifecycle event @@ -154,6 +162,30 @@ export default createReactClass({ this._launchManageIntegrations(); }, + _createResizer: function() { + if (!this.resizeContainer) { + return; + } + + const classNames = { + handle: "mx_ResizeHandle", + vertical: "mx_ResizeHandle_vertical", + resizing: "mx_AppsDrawer_resizing", + }; + const resizer = new Resizer( + this.resizeContainer, + FixedDistributor, + {}, + ); + resizer.setClassNames(classNames); + resizer.attach(); + this.resizer = resizer; + }, + + _setResizeContainerRef: function(div) { + this.resizeContainer = div; + }, + render: function() { const apps = this.state.apps.map((app, index, arr) => { const capWhitelist = WidgetUtils.getCapWhitelistForAppTypeInRoomId(app.type, this.props.room.roomId); @@ -191,6 +223,13 @@ export default createReactClass({ ; } + const containerStyle = { + maxHeight: Math.max(this.props.maxHeight - 50, 300), + }; + if (!this.props.showApps && this.resizer) { + this.resizer.forHandleAt(0).item.clearSize(); + } + let spinner; if ( apps.length === 0 && WidgetEchoStore.roomHasPendingWidgets( @@ -202,12 +241,20 @@ export default createReactClass({ spinner = ; } + const classes = classNames({ + "mx_AppsDrawer": true, + "mx_AppsDrawer_hidden": this.props.hide, + "mx_AppsDrawer_fullWidth": apps.length < 2, + "mx_AppsDrawer_minimised": !this.props.showApps, + }); + return ( -
-
+
+
{ apps } { spinner }
+ { this._canUserModify() && addWidget }
); From 735826015daa75a7cd385a6f206dff283f4cbc0b Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Thu, 30 Apr 2020 20:29:08 +0300 Subject: [PATCH 004/286] Make AppsDrawer resize handle easier to grab --- res/css/views/rooms/_AppsDrawer.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/res/css/views/rooms/_AppsDrawer.scss b/res/css/views/rooms/_AppsDrawer.scss index 3a33b73ec9..d6fb055bc0 100644 --- a/res/css/views/rooms/_AppsDrawer.scss +++ b/res/css/views/rooms/_AppsDrawer.scss @@ -386,6 +386,11 @@ form.mx_Custom_Widget_Form div { } /* Hidden resize handle (Apptile bottom serves as indicator) */ +.mx_AppsDrawer .mx_ResizeHandle { + position: relative; + z-index: 1; +} + .mx_AppsDrawer .mx_ResizeHandle > div { background: inherit; } From cca5ccd79d1e6ce6cdedf1d7f8ce49e2a59de87a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 21 Aug 2020 16:29:07 +0100 Subject: [PATCH 005/286] Switch widget resizing to re-resizable and add persistence --- res/css/views/rooms/_AppsDrawer.scss | 61 +++++++++----- res/css/views/voip/_CallContainer.scss | 4 + src/components/structures/RoomView.js | 4 +- .../views/elements/PersistedElement.js | 8 +- src/components/views/rooms/AppsDrawer.js | 79 ++++++++----------- src/hooks/useLocalStorage.ts | 37 +++++++++ 6 files changed, 124 insertions(+), 69 deletions(-) create mode 100644 src/hooks/useLocalStorage.ts diff --git a/res/css/views/rooms/_AppsDrawer.scss b/res/css/views/rooms/_AppsDrawer.scss index aae255f81a..348fb853d4 100644 --- a/res/css/views/rooms/_AppsDrawer.scss +++ b/res/css/views/rooms/_AppsDrawer.scss @@ -15,16 +15,43 @@ See the License for the specific language governing permissions and limitations under the License. */ -/* - Size settings -*/ -$AppsDrawerMinHeight: 50px; -$AppsDrawerDefaultHeight: 300px; $MiniAppTileHeight: 114px; .mx_AppsDrawer { - margin: 5px; - display: block; + margin: 5px 5px 5px 18px; + position: relative; + display: flex; + flex-direction: column; + overflow: hidden; + + .mx_RoomSublist_resizerHandles { + flex: 0 0 4px; + } + + .mx_RoomSublist_resizerHandle { + cursor: ns-resize; + border-radius: 3px; + + // Override styles from library + width: unset !important; + height: 4px !important; + + // This is positioned directly below frame + position: absolute; + bottom: -8px !important; // override from library + + // Together, these make the bar 64px wide + // These are also overridden from the library + left: calc(50% - 32px) !important; + right: calc(50% - 32px) !important; + } + + &:hover { + .mx_RoomSublist_resizerHandle { + opacity: 0.8; + background: $primary-fg-color; + } + } } .mx_AppsDrawer_hidden { @@ -36,13 +63,13 @@ $MiniAppTileHeight: 114px; flex-direction: row; align-items: stretch; justify-content: center; - min-height: $AppsDrawerMinHeight; - height: $AppsDrawerDefaultHeight; + height: 100%; } .mx_AppsDrawer_minimised .mx_AppsContainer { - min-height: inherit; - height: inherit; + // override the re-resizable inline styles + height: inherit !important; + min-height: inherit !important; } .mx_AddWidget_button { @@ -70,15 +97,14 @@ $MiniAppTileHeight: 114px; .mx_AppTile { max-width: 960px; width: 50%; - margin-right: 5px; border: 5px solid $widget-menu-bar-bg-color; border-radius: 4px; display: flex; flex-direction: column; -} -.mx_AppTile:last-child { - margin-right: 1px; + & + .mx_AppTile { + margin-left: 5px; + } } .mx_AppTileFullWidth { @@ -105,7 +131,7 @@ $MiniAppTileHeight: 114px; .mx_AppTile.mx_AppTile_minimised, .mx_AppTileFullWidth.mx_AppTile_minimised, .mx_AppTile_mini.mx_AppTile_minimised { - min-height: inherit; + height: 14px; } .mx_AppTile .mx_AppTile_persistedWrapper, @@ -117,7 +143,6 @@ $MiniAppTileHeight: 114px; .mx_AppTile_persistedWrapper div { width: 100%; height: 100%; - min-width: 300px; } .mx_AppTileMenuBar { @@ -402,7 +427,7 @@ form.mx_Custom_Widget_Form div { margin-right: auto; } -.mx_AppsDrawer_minimised .mx_ResizeHandle { +.mx_AppsDrawer_minimised .mx_RoomSublist_resizerHandle { display: none; } diff --git a/res/css/views/voip/_CallContainer.scss b/res/css/views/voip/_CallContainer.scss index 8d1b68dd99..4d26d8a312 100644 --- a/res/css/views/voip/_CallContainer.scss +++ b/res/css/views/voip/_CallContainer.scss @@ -36,6 +36,10 @@ limitations under the License. } } + .mx_AppTile_persistedWrapper div { + min-width: 300px; + } + .mx_IncomingCallBox { min-width: 250px; background-color: $primary-bg-color; diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index a79e5b0aa8..7605bdbfc0 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -1547,9 +1547,9 @@ export default createReactClass({ // header + footer + status + give us at least 120px of scrollback at all times. let auxPanelMaxHeight = window.innerHeight - - (83 + // height of RoomHeader + (54 + // height of RoomHeader 36 + // height of the status area - 72 + // minimum height of the message compmoser + 51 + // minimum height of the message compmoser 120); // amount of desired scrollback // XXX: this is a bit of a hack and might possibly cause the video to push out the page anyway diff --git a/src/components/views/elements/PersistedElement.js b/src/components/views/elements/PersistedElement.js index 7f9bfdebf4..9a64b7c7c4 100644 --- a/src/components/views/elements/PersistedElement.js +++ b/src/components/views/elements/PersistedElement.js @@ -17,7 +17,7 @@ limitations under the License. import React from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; - +import {throttle} from "lodash"; import ResizeObserver from 'resize-observer-polyfill'; import dis from '../../../dispatcher/dispatcher'; @@ -156,7 +156,7 @@ export default class PersistedElement extends React.Component { child.style.display = visible ? 'block' : 'none'; } - updateChildPosition(child, parent) { + updateChildPosition = throttle((child, parent) => { if (!child || !parent) return; const parentRect = parent.getBoundingClientRect(); @@ -167,9 +167,9 @@ export default class PersistedElement extends React.Component { width: parentRect.width + 'px', height: parentRect.height + 'px', }); - } + }, 100, {trailing: true, leading: true}); render() { - return
; + return
; } } diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index f173d76f24..502cbdc692 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -31,8 +31,8 @@ import AccessibleButton from '../elements/AccessibleButton'; import {IntegrationManagers} from "../../../integrations/IntegrationManagers"; import SettingsStore from "../../../settings/SettingsStore"; import classNames from 'classnames'; -import ResizeHandle from '../elements/ResizeHandle'; -import {Resizer, FixedDistributor} from '../../../resizer'; +import {Resizable} from "re-resizable"; +import {useLocalStorageState} from "../../../hooks/useLocalStorage"; // The maximum number of widgets that can be added in a room const MAX_WIDGETS = 2; @@ -63,7 +63,6 @@ export default createReactClass({ MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents); WidgetEchoStore.on('update', this._updateApps); this.dispatcherRef = dis.register(this.onAction); - this._createResizer(); }, componentWillUnmount: function() { @@ -73,10 +72,6 @@ export default createReactClass({ } WidgetEchoStore.removeListener('update', this._updateApps); if (this.dispatcherRef) dis.unregister(this.dispatcherRef); - if (this.resizer) { - this.resizer.detach(); - this.resizer = null; - } }, // TODO: [REACT-WARNING] Replace with appropriate lifecycle event @@ -162,30 +157,6 @@ export default createReactClass({ this._launchManageIntegrations(); }, - _createResizer: function() { - if (!this.resizeContainer) { - return; - } - - const classNames = { - handle: "mx_ResizeHandle", - vertical: "mx_ResizeHandle_vertical", - resizing: "mx_AppsDrawer_resizing", - }; - const resizer = new Resizer( - this.resizeContainer, - FixedDistributor, - {}, - ); - resizer.setClassNames(classNames); - resizer.attach(); - this.resizer = resizer; - }, - - _setResizeContainerRef: function(div) { - this.resizeContainer = div; - }, - render: function() { const apps = this.state.apps.map((app, index, arr) => { const capWhitelist = WidgetUtils.getCapWhitelistForAppTypeInRoomId(app.type, this.props.room.roomId); @@ -193,7 +164,7 @@ export default createReactClass({ return (); }); - if (apps.length == 0) { - return
; + if (apps.length === 0) { + return
; } let addWidget; @@ -223,13 +194,6 @@ export default createReactClass({ ; } - const containerStyle = { - maxHeight: Math.max(this.props.maxHeight - 50, 300), - }; - if (!this.props.showApps && this.resizer) { - this.resizer.forHandleAt(0).item.clearSize(); - } - let spinner; if ( apps.length === 0 && WidgetEchoStore.roomHasPendingWidgets( @@ -249,14 +213,39 @@ export default createReactClass({ }); return ( -
-
+
+ { apps } { spinner } -
- + { this._canUserModify() && addWidget }
); }, }); + +const PersistentVResizer = ({id, minHeight, maxHeight, className, handleWrapperClass, handleClass, children}) => { + const [height, setHeight] = useLocalStorageState("pvr_" + id, 100); + + return { + setHeight(height + d.height); + }} + handleWrapperClass={handleWrapperClass} + handleClasses={{bottom: handleClass}} + className={className} + enable={{bottom: true}} + > + { children } + ; +}; diff --git a/src/hooks/useLocalStorage.ts b/src/hooks/useLocalStorage.ts new file mode 100644 index 0000000000..2f110c58f2 --- /dev/null +++ b/src/hooks/useLocalStorage.ts @@ -0,0 +1,37 @@ +/* +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 {useEffect, useRef, useState} from "react"; + +// Hook behaving like useState but persisting the value to localStorage. Returns same as useState +export const useLocalStorageState = (key: string, initialValue: boolean) => { + const lsKey = useRef("useLocalStorageState_" + key).current; + + const [value, setValue] = useState(() => { + try { + const item = window.localStorage.getItem(lsKey); + return item ? JSON.parse(item) : initialValue; + } catch (error) { + return initialValue; + } + }); + + useEffect(() => { + window.localStorage.setItem(lsKey, JSON.stringify(value)); + }, [lsKey, value]); + + return [value, setValue]; +}; From 35cc1fb06d51290db8da256b773d4be77b101fb7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 21 Aug 2020 16:38:28 +0100 Subject: [PATCH 006/286] small tweaks --- res/css/views/rooms/_AppsDrawer.scss | 8 ++++---- src/components/views/rooms/AppsDrawer.js | 15 +++++++++++---- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/res/css/views/rooms/_AppsDrawer.scss b/res/css/views/rooms/_AppsDrawer.scss index 348fb853d4..1d62e0a9b4 100644 --- a/res/css/views/rooms/_AppsDrawer.scss +++ b/res/css/views/rooms/_AppsDrawer.scss @@ -24,11 +24,11 @@ $MiniAppTileHeight: 114px; flex-direction: column; overflow: hidden; - .mx_RoomSublist_resizerHandles { + .mx_AppsContainer_resizerHandles { flex: 0 0 4px; } - .mx_RoomSublist_resizerHandle { + .mx_AppsContainer_resizerHandle { cursor: ns-resize; border-radius: 3px; @@ -47,7 +47,7 @@ $MiniAppTileHeight: 114px; } &:hover { - .mx_RoomSublist_resizerHandle { + .mx_AppsContainer_resizerHandle { opacity: 0.8; background: $primary-fg-color; } @@ -427,7 +427,7 @@ form.mx_Custom_Widget_Form div { margin-right: auto; } -.mx_AppsDrawer_minimised .mx_RoomSublist_resizerHandle { +.mx_AppsDrawer_minimised .mx_AppsContainer_resizerHandle { display: none; } diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index 502cbdc692..3a60f21f9c 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; +import React, {useState} from 'react'; import PropTypes from 'prop-types'; import createReactClass from 'create-react-class'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; @@ -218,8 +218,8 @@ export default createReactClass({ id={"apps-drawer_" + this.props.room.roomId} minHeight={100} maxHeight={this.props.maxHeight - 50} - handleWrapperClass="mx_RoomSublist_resizerHandles" - handleClass="mx_RoomSublist_resizerHandle" + handleWrapperClass="mx_AppsContainer_resizerHandles" + handleClass="mx_AppsContainer_resizerHandle" className="mx_AppsContainer" > { apps } @@ -233,17 +233,24 @@ export default createReactClass({ const PersistentVResizer = ({id, minHeight, maxHeight, className, handleWrapperClass, handleClass, children}) => { const [height, setHeight] = useLocalStorageState("pvr_" + id, 100); + const [resizing, setResizing] = useState(false); return { + setResizing(true); + }} onResizeStop={(e, dir, ref, d) => { setHeight(height + d.height); + setResizing(false); }} handleWrapperClass={handleWrapperClass} handleClasses={{bottom: handleClass}} - className={className} + className={classNames(className, { + mx_AppsDrawer_resizing: resizing, + })} enable={{bottom: true}} > { children } From 97d8cec94e29e315b1e725f1d87b74d5edfacaef Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 21 Aug 2020 16:40:05 +0100 Subject: [PATCH 007/286] dedup --- src/components/views/rooms/AppsDrawer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index 3a60f21f9c..9b37dd3f4f 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -240,11 +240,11 @@ const PersistentVResizer = ({id, minHeight, maxHeight, className, handleWrapperC minHeight={minHeight} maxHeight={maxHeight} onResizeStart={() => { - setResizing(true); + if (!resizing) setResizing(true); }} onResizeStop={(e, dir, ref, d) => { setHeight(height + d.height); - setResizing(false); + if (resizing) setResizing(false); }} handleWrapperClass={handleWrapperClass} handleClasses={{bottom: handleClass}} From dc08fee635641b1fa2eda45e08e221e8ff543d0a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 24 Aug 2020 14:38:58 +0100 Subject: [PATCH 008/286] Fix the resize handle being unreachable when you don't have permission to add widgets --- res/css/views/rooms/_AppsDrawer.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/res/css/views/rooms/_AppsDrawer.scss b/res/css/views/rooms/_AppsDrawer.scss index 1d62e0a9b4..13130fc6f6 100644 --- a/res/css/views/rooms/_AppsDrawer.scss +++ b/res/css/views/rooms/_AppsDrawer.scss @@ -64,6 +64,7 @@ $MiniAppTileHeight: 114px; align-items: stretch; justify-content: center; height: 100%; + margin-bottom: 8px; } .mx_AppsDrawer_minimised .mx_AppsContainer { @@ -76,7 +77,7 @@ $MiniAppTileHeight: 114px; order: 2; cursor: pointer; padding: 0; - margin: 5px auto 5px 0; + margin: -3px auto 5px 0; color: $accent-color; font-size: $font-12px; } From 6da4f407e54fa5bb570b8ffd4de0a5cb9e772a6d Mon Sep 17 00:00:00 2001 From: Lizzy Date: Wed, 26 Aug 2020 14:02:46 +0000 Subject: [PATCH 009/286] Translated using Weblate (Spanish) Currently translated at 87.7% (2050 of 2337 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/es/ --- src/i18n/strings/es.json | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/src/i18n/strings/es.json b/src/i18n/strings/es.json index 1619bb7616..29f66eb57e 100644 --- a/src/i18n/strings/es.json +++ b/src/i18n/strings/es.json @@ -50,7 +50,7 @@ "Deops user with given id": "Degrada al usuario con la ID dada", "Default": "Por Defecto", "Disinvite": "Deshacer invitación", - "Displays action": "Muestra la acción", + "Displays action": "Hacer una acción", "Download %(text)s": "Descargar %(text)s", "Email": "Correo electrónico", "Email address": "Dirección de correo electrónico", @@ -285,7 +285,7 @@ "Uploading %(filename)s and %(count)s others|one": "Subiendo %(filename)s y otros %(count)s", "Uploading %(filename)s and %(count)s others|other": "Subiendo %(filename)s y otros %(count)s", "Upload avatar": "Subir avatar", - "Upload Failed": "No Se Pudo Subir", + "Upload Failed": "Subida fallida", "Upload file": "Subir archivo", "Upload new:": "Subir nuevo:", "Usage": "Uso", @@ -518,7 +518,7 @@ "Failed to invite the following users to %(groupId)s:": "No se pudo invitar a los siguientes usuarios a %(groupId)s:", "Failed to invite users to community": "No se pudo invitar usuarios a la comunidad", "Failed to invite users to %(groupId)s": "No se pudo invitar usuarios a %(groupId)s", - "Failed to add the following rooms to %(groupId)s:": "No se pudo añadir a las siguientes salas a %(groupId)s:", + "Failed to add the following rooms to %(groupId)s:": "No se pudieron añadir las siguientes salas a %(groupId)s:", "Restricted": "Restringido", "Missing roomId.": "Falta el Id de sala.", "Ignores a user, hiding their messages from you": "Ignora a un usuario, ocultando sus mensajes", @@ -1246,7 +1246,7 @@ "Are you sure you want to sign out?": "¿Estás seguro de que quieres salir?", "Message edits": "Ediciones del mensaje", "New session": "Nueva sesión", - "Use this session to verify your new one, granting it access to encrypted messages:": "Usa esta sesión para verificar tu nueva sesión, dándola acceso a mensajes encriptados:", + "Use this session to verify your new one, granting it access to encrypted messages:": "Usa esta sesión para verificar tu nueva sesión, dándole acceso a mensajes encriptados:", "If you didn’t sign in to this session, your account may be compromised.": "Si no te conectaste a esta sesión, es posible que tu cuenta haya sido comprometida.", "This wasn't me": "No fui yo", "If you run into any bugs or have feedback you'd like to share, please let us know on GitHub.": "Si encuentras algún error o quieres compartir una opinión, por favor, contacta con nosotros en GitHub.", @@ -1318,7 +1318,7 @@ "Your user agent": "Tu agente de usuario", "If you cancel now, you won't complete verifying the other user.": "Si cancelas ahora, no completarás la verificación del otro usuario.", "If you cancel now, you won't complete verifying your other session.": "Si cancelas ahora, no completarás la verificación de tu otra sesión.", - "Cancel entering passphrase?": "¿Cancelar la introducción de frase de contraseña?", + "Cancel entering passphrase?": "¿Cancelar el ingresar tu contraseña de recuperación?", "%(senderName)s updated the rule banning rooms matching %(glob)s for %(reason)s": "%(senderName)s actualizó la regla bloqueando salas que coinciden con %(glob)s por %(reason)s", "%(senderName)s updated the rule banning servers matching %(glob)s for %(reason)s": "%(senderName)s actualizó la regla bloqueando servidores que coinciden con %(glob)s por %(reason)s", "%(senderName)s updated a ban rule matching %(glob)s for %(reason)s": "%(senderName)s actualizó una regla de bloqueo correspondiente a %(glob)s por %(reason)s", @@ -1436,7 +1436,7 @@ "If you cancel now, you won't complete your operation.": "Si cancela ahora, no completará la operación.", "Review where you’re logged in": "Revise dónde hizo su registro", "New login. Was this you?": "Nuevo registro. ¿Fuiste tú?", - "%(name)s is requesting verification": "%(name)s solicita verificación", + "%(name)s is requesting verification": "%(name)s solicita verificación", "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", @@ -1614,7 +1614,7 @@ "Can't find this server or its room list": "No puedo encontrar este servidor o su lista de salas", "All rooms": "Todas las salas", "Your server": "Tu", - "Are you sure you want to remove %(serverName)s": "¿ Está seguro de querer eliminar %(serverName)s?", + "Are you sure you want to remove %(serverName)s": "¿Está seguro de querer eliminar %(serverName)s?", "Remove server": "Quitar servidor", "Matrix": "Matrix", "Add a new server": "Añadir un nuevo servidor", @@ -1914,7 +1914,7 @@ "Unable to restore backup": "No se pudo restaurar la copia de seguridad", "No backup found!": "¡No se encontró una copia de seguridad!", "Keys restored": "Se restauraron las claves", - "Failed to decrypt %(failedCount)s sessions!": "¡Error en descifrar %(failedCount) sesiones!", + "Failed to decrypt %(failedCount)s sessions!": "¡Error al descifrar %(failedCount)s sesiones!", "Successfully restored %(sessionCount)s keys": "%(sessionCount)s claves restauradas con éxito", "Warning: you should only set up key backup from a trusted computer.": "Advertencia: deberías configurar la copia de seguridad de claves solamente usando un ordenador de confianza.", "Access your secure message history and set up secure messaging by entering your recovery passphrase.": "Acceda a su historial de mensajes seguros y configure la mensajería segura introduciendo su contraseña de recuperación.", @@ -2075,5 +2075,27 @@ "%(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" + "You invited %(targetName)s": "Has invitado a %(targetName)s", + "Are you sure you want to cancel entering passphrase?": "¿Estas seguro que quieres cancelar el ingresar tu contraseña de recuperación?", + "Go Back": "No cancelar", + "Joins room with given address": "Entrar a la sala con la dirección especificada", + "Unrecognised room address:": "No se encuentra la dirección de la sala:", + "Opens chat with the given user": "Abrir una conversación con el usuario especificado", + "Sends a message to the given user": "Enviar un mensaje al usuario especificado", + "Light": "Claro", + "Dark": "Oscuro", + "Unexpected server error trying to leave the room": "Error inesperado del servidor al abandonar esta sala", + "Error leaving room": "Error al salir de la sala", + "Your homeserver has exceeded its user limit.": "Tú servidor ha excedido su limite de usuarios.", + "Your homeserver has exceeded one of its resource limits.": "Tú servidor ha excedido el limite de sus recursos.", + "Contact your server admin.": "Contacta con el administrador del servidor.", + "The person who invited you already left the room.": "La persona que te invito abandono la sala.", + "The person who invited you already left the room, or their server is offline.": "La persona que te invito abandono la sala, o puede que su servidor se encuentre desconectado.", + "* %(senderName)s %(emote)s": "* %(senderName)s %(emote)s", + "%(senderName)s: %(message)s": "%(senderName)s: %(message)s", + "%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s", + "%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s", + "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Tu nueva sesión se encuentra verificada ahora. Ahora tiene acceso a los mensajes encriptados y otros usuarios verán la sesión como verificada.", + "Your new session is now verified. Other users will see it as trusted.": "Tu sesión se encuentra ahora verificada. Otros usuarios la verán como confiable.", + "This session is encrypting history using the new recovery method.": "Esta sesión se encuentra encriptando el historial usando el nuevo método de verificación." } From 01eee3008901b08616c4e508cccbf47216d8f981 Mon Sep 17 00:00:00 2001 From: ziriSut Date: Wed, 26 Aug 2020 16:09:50 +0000 Subject: [PATCH 010/286] Translated using Weblate (Kabyle) Currently translated at 89.6% (2109 of 2353 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/kab/ --- src/i18n/strings/kab.json | 87 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/kab.json b/src/i18n/strings/kab.json index 9af55c0793..51fe8724bf 100644 --- a/src/i18n/strings/kab.json +++ b/src/i18n/strings/kab.json @@ -649,7 +649,7 @@ "Connecting to integration manager...": "Tuqqna ɣer umsefrak n useddu...", "Cannot connect to integration manager": "Ur nessaweḍ ara ad neqqen ɣer umsefrak n useddu", "Delete Backup": "Kkes aḥraz", - "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Iznan yettwawgelhen ttuḥerzen s uwgelhen n yixef ɣer yixef. Ala kečč d unermas (inermasen) i yesεan tisura akken ad ɣren iznan-a.", + "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Iznan yettwawgelhen ttuḥerzen s uwgelhen n yixef ɣer yixef. Ala kečč d uɣerwaḍ (yiɣerwaḍen) i yesεan tisura akken ad ɣren iznan-a.", "This session is backing up your keys. ": "Tiɣimit tḥerrez tisura-inek·inem. ", "Connect this session to Key Backup": "Qqen tiɣimit-a ɣer uḥraz n tsarut", "Server Name": "Isem n uqeddac", @@ -2074,5 +2074,88 @@ "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.": "War aswel n Secure Message Recovery, ad tmedleḍ amazray-ik·im n yiznan uffiren ma yella teffɣeḍ.", "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.": "Ma yella ur tesbaduḍ ara tarrayt n tririt tamaynut, yezmer ad yili umaker ara iɛerḍen ad yekcem ɣer umiḍan-ik·im. Beddel awal uffir n umiḍan-ik·im syen sbadu tarrayt n tririt tamaynut din din deg yiɣewwaren.", "This session has detected that your recovery passphrase and key for Secure Messages have been removed.": "Tiɣimit-a tufa-d tafyirt-ik·im tuffirt n tririt d tsarut-ik·im n yiznan uffiren ttwakksent.", - "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.": "Ma yella ur tekkiseḍ ara tarrayt n tririt tamaynut, yezmer ad yili umaker ara iɛerḍen ad yekcem ɣer umiḍan-ik·im. Beddel awal uffir n umiḍan-ik·im syen sbadu tarrayt n tririt tamaynut din din deg yiɣewwaren." + "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.": "Ma yella ur tekkiseḍ ara tarrayt n tririt tamaynut, yezmer ad yili umaker ara iɛerḍen ad yekcem ɣer umiḍan-ik·im. Beddel awal uffir n umiḍan-ik·im syen sbadu tarrayt n tririt tamaynut din din deg yiɣewwaren.", + "Mirror local video feed": "Asbani n usuddem n tvidyut tadigant", + "Low bandwidth mode": "Askar n tehri n tesfift adday", + "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)": "Sireg aqeddac n tallalt i yisawalen n ufran aneggaru turn.matrix.org ma yili aqeddac-ik·im agejdan ur d-yettmudd ara yiwen (tansa-ik·im n IP ad tettwabḍu lawan n usiwel)", + "Compare a unique set of emoji if you don't have a camera on either device": "Serwes tagrumma n yimujiten asufen ma yella ur tesɛiḍ ara takamiṛat ɣef yiwen seg sin yibenkan", + "Unable to find a supported verification method.": "D awezɣi ad d-naf tarrayt n usenqed yettusefraken.", + "Waiting for your other session, %(deviceName)s (%(deviceId)s), to verify…": "Deg uṛaǧu n tɣimit-ik·im tayeḍ, %(deviceName)s (%(deviceId)s), i usenqed…", + "To be secure, do this in person or use a trusted way to communicate.": "I wakken ad tḍemneḍ taɣellistik·im, eg ayagi s timmad-ik·im neɣ seqdec abrid n teywalt iɣef ara tettekleḍ.", + "You may need to manually permit %(brand)s to access your microphone/webcam": "Ilaq-ak·am ahat ad tesirgeḍ s ufus %(brand)s i unekcum ɣer usawaḍ/webcam", + "This room is not accessible by remote Matrix servers": "Anekcum er texxamt-a ulamek s yiqeddacen n Matrix inmeggagen", + "No users have specific privileges in this room": "Ulac aqeddac yesan ibauren deg texxamt-a ", + "Select the roles required to change various parts of the room": "Fren timlilin yettusran i usnifel n yiḥricen yemgaraden n texxamt", + "Guests cannot join this room even if explicitly invited.": "Ur zmiren ara inebgawen ad d-rnun ɣer texxamt-a alamma ttusnubegten-d s tidet.", + "Once enabled, encryption cannot be disabled.": "Akken ara yettwarmad, awgelhen ur yettizmir ara ad yens.", + "Click the link in the email you received to verify and then click continue again.": "Sit ɣef useɣwen yella deg yimayl i teṭṭfeḍ i usenqed syen sit tikkelt tayeḍ ad tkemmleḍ.", + "Discovery options will appear once you have added an email above.": "Tixtiṛiyin n usnirem ad d-banent akken ara ternuḍ imayl s ufella.", + "Discovery options will appear once you have added a phone number above.": "Tixtiṛiyin n usnirem ad d-banent akken ara ternuḍ uṭṭun n tilifun s ufella.", + "The maximum permitted number of widgets have already been added to this room.": "Amḍan afellay yettusirgen n yiwiǧiten yettwarna yakan ɣer texxamt-a.", + "This room doesn't exist. Are you sure you're at the right place?": "Taxxamt-a ulac-itt. Tetteḥqeḍ aql-ak·akem deg wadeg i iṣeḥḥan?", + "Try again later, or ask a room admin to check if you have access.": "Ɛreḍ tikkelt-nniḍen ticki, neɣ suter deg unedbal n texxamt ad iwali ma tzemreḍ ad tkecmeḍ.", + "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please submit a bug report.": "%(errcode)s yuɣal-d lawan n uneɛruḍ n unekcum ɣer texxamt. Ma yella izen-a twalaḍ-t ur tebniḍ fell-as, ttxil-k·m azen aneqqis n wabug.", + "Never lose encrypted messages": "Ur ttamdal ara akk iznan iwgelhanen", + "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Iznan deg texxamt-a ttwaḥerzen s uwgelhen n yixef ɣer yixef. Ala kečč·kemm d uɣerwaḍ (yiɣerwaḍen) i yesεan tisura akken ad ɣren iznan-a.", + "Securely back up your keys to avoid losing them. Learn more.": "Ḥrez tisura-k·m s wudem aɣelsan i wakken ur ak·am-ttṛuḥunt ara. Issin ugar", + "Unrecognised command: %(commandText)s": "Taladna d tarussint: %(commandText)s", + "Hint: Begin your message with // to start it with a slash.": "Taxballut: Bdu izen-ik·im s // i wakken ad t-tebduḍ s uṣlac.", + "Failed to connect to integration manager": "Tuqqna ɣer umsefrak n umsidef ur yeddi ara", + "Jump to first unread message.": "Ɛeddi ɣer yzen amezwaru ur nettwaɣra ara. ", + "Error updating main address": "Tuccḍa deg usali n tensa tagejdant", + "You don't have permission to delete the address.": "Ur tesɛiḍ ara tisirag i wakken ad tekkseḍ tansa.", + "This room has no local addresses": "Taxxamt-a ur tesɛi ara tansiwin tidiganin", + "Error updating flair": "Tuccḍa deg uleqqem n lbenna", + "'%(groupId)s' is not a valid community ID": "'%(groupId)s' mačči d asulay n temɣiwent ameɣtu", + "Use bots, bridges, widgets and sticker packs": "Seqdec abuten, tileggiyin, iwiǧiten d tɣawsiwin n umyintaḍ", + "To continue you need to accept the terms of this service.": "I wakken ad tkemmleḍ tesriḍ ad tqebleḍ tiwtilin n umeẓlu-a.", + "This file is too large to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.": "Afaylu-a ɣezzif aṭas i wakken ad d-yali. Talast n teɣzi n ufaylu d %(limit)s.", + "These files are too large to upload. The file size limit is %(limit)s.": "Ifuyla-a ɣezzifit aṭas i wakken ad d-alin. Talast n teɣzi n ufaylu d %(limit)s.", + "Some files are too large to be uploaded. The file size limit is %(limit)s.": "Kra n yifuyla ɣezzifit aṭas i wakken ad d-alin. Talast n teɣzi n ufaylu d %(limit)s.", + "Upload %(count)s other files|other": "Sali-d %(count)s ifuyla-nniḍen", + "Upload %(count)s other files|one": "Sali-d %(count)s afaylu-nniḍen", + "Upload Error": "Tuccḍa deg usali", + "A widget would like to verify your identity": "Awiǧit yebɣa ad issenqed timagit-inek·inem", + "Remember my selection for this widget": "Cfu ɣef tefrant-inu i uwiǧit-a", + "Wrong file type": "Anaw n yifuyla d arameɣtu", + "Looks good!": "Yettban igerrez!", + "Enter your Security Phrase or to continue.": "Sekcem tafyirt-ik·im n tɣellist neɣ ": "Ma yella tettuḍ tasarut-ik·im n uɛeddi, tzemreḍ ", + "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|other": "Wennez kullec neɣ sefsex kullec tura. Tzemreḍ daɣen ad tferneḍ iznan udmawanen i uwennez neɣ i usefsex.", + "Tried to load a specific point in this room's timeline, but was unable to find it.": "Ɛerḍeɣ ad d-saliɣ tazmilt tufrint tesnakudt n texxamt-a, maca ur ssawḍeɣ ara ad t-naf.", + "A verification email will be sent to your inbox to confirm setting your new password.": "Imayl n usenqed ad yettwazen ɣer tbewwaḍt-ik·im n yimayl i usentem n yiɣewwaren n wawal-ik·im uffir." } From a97d1ca34490c6543926d90ac40efe2ab647a8d8 Mon Sep 17 00:00:00 2001 From: Nikita Epifanov Date: Fri, 28 Aug 2020 12:11:13 +0000 Subject: [PATCH 036/286] Translated using Weblate (Russian) Currently translated at 100.0% (2356 of 2356 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index 2fa00ca92a..b6a2f483fb 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -2428,7 +2428,7 @@ "Show": "Показать", "Send %(count)s invites|other": "Отправить %(count)s приглашений", "Send %(count)s invites|one": "Отправить %(count)s приглашение", - "Invite people to join %(communityName)s": "Пригласите людей присоединиться к %(communtyName)s", + "Invite people to join %(communityName)s": "Пригласите людей присоединиться к %(communityName)s", "There was an error creating your community. The name may be taken or the server is unable to process your request.": "При создании сообщества произошла ошибка. Имя может быть занято или сервер не может обработать ваш запрос.", "Community ID: +:%(domain)s": "ID сообщества: +:%(domain)s", "Use this when referencing your community to others. The community ID cannot be changed.": "Используйте это при обращении к другим людям. ID сообщества не может быть изменён.", @@ -2438,5 +2438,7 @@ "Add image (optional)": "Добавить изображение (необязательно)", "An image will help people identify your community.": "Изображение поможет людям идентифицировать ваше сообщество.", "Create a room in %(communityName)s": "Создать комнату в %(communityName)s", - "Create community": "Создать сообщество" + "Create community": "Создать сообщество", + "Cross-signing and secret storage are ready for use.": "Кросс-подпись и секретное хранилище готовы к использованию.", + "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "Кросс-подпись готова к использованию, но секретное хранилище в настоящее время не используется для резервного копирования ваших ключей." } From 0ea3bd820768a5a91b395146a28d4723cddd62d9 Mon Sep 17 00:00:00 2001 From: ziriSut Date: Fri, 28 Aug 2020 15:49:48 +0000 Subject: [PATCH 037/286] Translated using Weblate (Kabyle) Currently translated at 99.0% (2335 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/kab/ --- src/i18n/strings/kab.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/kab.json b/src/i18n/strings/kab.json index b66616d07a..ad2e683faa 100644 --- a/src/i18n/strings/kab.json +++ b/src/i18n/strings/kab.json @@ -2373,5 +2373,19 @@ "If you've forgotten your recovery key you can ": "Ma yella tettuḍ tasarut-ik·im n uɛeddi, tzemreḍ ", "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|other": "Wennez kullec neɣ sefsex kullec tura. Tzemreḍ daɣen ad tferneḍ iznan udmawanen i uwennez neɣ i usefsex.", "Tried to load a specific point in this room's timeline, but was unable to find it.": "Ɛerḍeɣ ad d-saliɣ tazmilt tufrint tesnakudt n texxamt-a, maca ur ssawḍeɣ ara ad t-naf.", - "A verification email will be sent to your inbox to confirm setting your new password.": "Imayl n usenqed ad yettwazen ɣer tbewwaḍt-ik·im n yimayl i usentem n yiɣewwaren n wawal-ik·im uffir." + "A verification email will be sent to your inbox to confirm setting your new password.": "Imayl n usenqed ad yettwazen ɣer tbewwaḍt-ik·im n yimayl i usentem n yiɣewwaren n wawal-ik·im uffir.", + "Access your secure message history and set up secure messaging by entering your recovery passphrase.": "Kcem ɣer umazray aɣelsan n yiznan-inek·inem syen sbadu tirawt taɣelsant s usekcem n tefyirt tuffirt n uɛeddi.", + "Access your secure message history and set up secure messaging by entering your recovery key.": "Kcem ɣer umazray aɣelsan n yiznan-ik·im syen sbadu tirawt taɣelsant s usekcem n tsarut n uɛeddi.", + "Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.": "Txuṣṣ tsarut tazayezt n captcha deg umtawi n uqeddac agejdan. Ttxil-k·m azen aneqqis ɣef waya i unedbal n uqeddac-ik·im agejdan.", + "Enter the location of your Element Matrix Services homeserver. It may use your own domain name or be a subdomain of element.io.": "Sekcem adeg n uqeddac-ik·im agejdan n umeẓlu n Element Matrix. Yezmer ad iseqdec isem n taɣult-ik·im uzzig neɣ ad yili d taɣult tarnawt n element.io.", + "No identity server is configured so you cannot add an email address in order to reset your password in the future.": "Ulac aqeddac n timagit yettusiwlen, ɣef waya ur tettizmireḍ ara ad ternuḍ tansa n yimayl i wakken ad twennzeḍ awal-ik·im uffir ɣer sdat.", + "If you don't specify an email address, you won't be able to reset your password. Are you sure?": "Ma yella ur d-tefrineḍ ara tansa n yimayl, ur tettizmireḍ ara ad twennzeḍ awal-ik·im uffir. Tebɣiḍ?", + "Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.": "Sbadu imayl i tririt n umiḍan. Seqdec imayl neɣ tiliɣri i wakken ad tettwaf s uxtiṛi sɣur inermisen i yellan.", + "Set an email for account recovery. Use email to optionally be discoverable by existing contacts.": "Sbadu imayl i tririt n umiḍan. Seqdec imayl s ufran i wakken ad d-iban i yinermisen i yellan.", + "%(brand)s uses many advanced browser features, some of which are not available or experimental in your current browser.": "%(brand)s isseqdac aṭas n tmahilin leqqayen n yiminig, kra seg-sent ulac-itent neɣ d tirmitanin deg yiminig-ik·im amiran.", + "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!": "S yiminig-ik·im amiran, ameẓraw d umḥulfan n usnas zemren ad ilin mačči akk d imeɣta, rnu kra neɣ akk timahilin zemrent ur teddunt ara. Ma yella tebɣiḍ ɣas akken ad t-tɛerḍeḍ tzemreḍ ad tkemmleḍ, maca ur tseɛɛuḍ ara akk tallalt ma yella temlaleḍ-d d wuguren!", + "You are an administrator of this community. You will not be able to rejoin without an invite from another administrator.": "Aql-ak·akem d (t)anedbal(t) n temɣiwent-a. Ur tettizmireḍ ara ad tɛawdeḍ anekcum alamma s tinubga n unedbal-nniḍen.", + "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.": "Isnifal i d-yellan ɣef isem d avaṭar i temɣiwent-ik·im ur ttmeẓran ara sɣur yiseqdacen-nniḍen alamma d 30 tesdidin .", + "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.": "Tixxamin-a ttwaskanent i yiɛeggalen n temɣiwent ɣef usebter n temɣiwent. Iɛeggalen n temɣiwent zemren ad ttekkin deg texxamin s usiti fell-asent.", + "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!": "Tamɣiwent-ik·im ur tesɛi ara aglam ɣezzifen, asebter HTML i uskan n yiɛeggalen n temɣiwent.
Sit da i wakken ad teldiḍ iɣewwaren syen rnu yiwet!" } From bc9ea04118c4c414854f56cac44540e60d9e5bd8 Mon Sep 17 00:00:00 2001 From: dkanada Date: Sat, 29 Aug 2020 20:29:43 +0900 Subject: [PATCH 038/286] add lenny face command --- src/SlashCommands.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index d674634109..0aacfb4340 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -154,6 +154,19 @@ export const Commands = [ }, category: CommandCategories.messages, }), + new Command({ + command: 'lenny', + args: '', + description: _td('Prepends ( ͡° ͜ʖ ͡°) to a plain-text message'), + runFn: function(roomId, args) { + let message = '( ͡° ͜ʖ ͡°)'; + if (args) { + message = message + ' ' + args; + } + return success(MatrixClientPeg.get().sendTextMessage(roomId, message)); + }, + category: CommandCategories.messages, + }), new Command({ command: 'plain', args: '', From 3703786d258de8ca26cedbdbf5288df2a4d0c6fc Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Fri, 28 Aug 2020 17:26:16 +0000 Subject: [PATCH 039/286] Translated using Weblate (Swedish) Currently translated at 74.3% (1751 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 222 +++++++++++++++++++++++++++++++-------- 1 file changed, 178 insertions(+), 44 deletions(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 0978ff6c25..16be70f1c8 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -72,7 +72,7 @@ "Export": "Exportera", "Export E2E room keys": "Exportera krypteringsrumsnycklar", "Failed to ban user": "Det gick inte att banna användaren", - "Failed to change password. Is your password correct?": "Det gick inte att byta lösenord. Är lösenordet rätt?", + "Failed to change password. Is your password correct?": "Misslyckades att byta lösenord. Är lösenordet rätt?", "Failed to change power level": "Det gick inte att ändra behörighetsnivå", "Failed to forget room %(errCode)s": "Det gick inte att glömma bort rummet %(errCode)s", "Failed to join room": "Det gick inte att gå med i rummet", @@ -84,7 +84,7 @@ "Failed to reject invitation": "Det gick inte att avböja inbjudan", "Failed to send email": "Det gick inte att skicka epost", "Failed to send request.": "Det gick inte att sända begäran.", - "Failed to set display name": "Det gick inte att ange visningsnamn", + "Failed to set display name": "Misslyckades att ange visningsnamn", "Failed to unban": "Det gick inte att avbanna", "Failed to verify email address: make sure you clicked the link in the email": "Det gick inte att bekräfta e-postadressen: set till att du klickade på länken i e-postmeddelandet", "Favourite": "Favorit", @@ -114,8 +114,8 @@ "Hangup": "Lägg på", "Historical": "Historiska", "Home": "Hem", - "Homeserver is": "Hemserver är", - "Identity Server is": "Identitetsserver är", + "Homeserver is": "Hemservern är", + "Identity Server is": "Identitetsservern är", "I have verified my email address": "Jag har verifierat min epostadress", "Import": "Importera", "Import E2E room keys": "Importera rumskrypteringsnycklar", @@ -138,7 +138,7 @@ "%(senderName)s kicked %(targetName)s.": "%(senderName)s kickade %(targetName)s.", "Kick": "Kicka", "Kicks user with given id": "Kickar användaren med givet ID", - "Labs": "Labb", + "Labs": "Experiment", "Last seen": "Senast sedd", "Leave room": "Lämna rummet", "%(targetName)s left the room.": "%(targetName)s lämnade rummet.", @@ -190,7 +190,7 @@ "Return to login screen": "Tillbaka till login-skärmen", "%(brand)s does not have permission to send you notifications - please check your browser settings": "%(brand)s har inte tillstånd att skicka aviseringar - kontrollera webbläsarens inställningar", "%(brand)s was not given permission to send notifications - please try again": "%(brand)s fick inte tillstånd att skicka aviseringar - försök igen", - "%(brand)s version:": "%(brand)s -version:", + "%(brand)s version:": "%(brand)s-version:", "Room %(roomId)s not visible": "Rummet %(roomId)s är inte synligt", "Room Colour": "Rumsfärg", "%(roomName)s does not exist.": "%(roomName)s finns inte.", @@ -300,7 +300,7 @@ "Waiting for response from server": "Väntar på svar från servern", "Leave": "Lämna", "Uploaded on %(date)s by %(user)s": "%(user)s laddade upp %(date)s", - "Advanced notification settings": "Avancerade aviseringsinställingar", + "Advanced notification settings": "Avancerade aviseringsinställningar", "Forget": "Glöm bort", "You cannot delete this image. (%(code)s)": "Du kan inte radera den här bilden. (%(code)s)", "Cancel Sending": "Avbryt sändning", @@ -312,12 +312,12 @@ "Messages in one-to-one chats": "Meddelanden i en-till-en chattar", "Unavailable": "Otillgänglig", "View Decrypted Source": "Visa dekrypterad källa", - "Failed to update keywords": "Det gick inte att uppdatera nyckelorden", + "Failed to update keywords": "Kunde inte uppdatera nyckelorden", "remove %(name)s from the directory.": "ta bort %(name)s från katalogen.", "Notifications on the following keywords follow rules which can’t be displayed here:": "Aviseringar för följande nyckelord följer regler som inte kan visas här:", "Please set a password!": "Vänligen välj ett lösenord!", "You have successfully set a password!": "Du har valt ett nytt lösenord!", - "An error occurred whilst saving your email notification preferences.": "Ett fel uppstod då epostaviseringsinställningarna sparades.", + "An error occurred whilst saving your email notification preferences.": "Ett fel inträffade då e-postaviseringsinställningarna sparades.", "Explore Room State": "Utforska rumläget", "Source URL": "Käll-URL", "Failed to add tag %(tagName)s to room": "Det gick inte att lägga till etiketten \"%(tagName)s\" till rummet", @@ -330,7 +330,7 @@ "Keywords": "Nyckelord", "Enable notifications for this account": "Aktivera aviseringar för det här kontot", "Messages containing keywords": "Meddelanden som innehåller nyckelord", - "Error saving email notification preferences": "Ett fel uppstod då epostaviseringsinställningarna sparades", + "Error saving email notification preferences": "Fel när e-postaviseringsinställningarna sparades", "Tuesday": "tisdag", "Enter keywords separated by a comma:": "Skriv in nyckelord, separerade med kommatecken:", "Search…": "Sök…", @@ -356,12 +356,12 @@ "Send logs": "Skicka loggar", "All messages": "Alla meddelanden", "Call invitation": "Inbjudan till samtal", - "Downloading update...": "Laddar ned uppdatering...", + "Downloading update...": "Laddar ned uppdatering…", "You have successfully set a password and an email address!": "Du har framgångsrikt valt ett lösenord och en e-postadress!", "What's new?": "Vad är nytt?", "Notify me for anything else": "Avisera för allt annat", "When I'm invited to a room": "När jag bjuds in till ett rum", - "Can't update user notification settings": "Kan inte uppdatera aviseringsinställningarna", + "Can't update user notification settings": "Kan inte uppdatera användaraviseringsinställningarna", "Notify for all other messages/rooms": "Avisera för alla andra meddelanden/rum", "Unable to look up room ID from server": "Det gick inte att hämta rums-ID:t från servern", "Couldn't find a matching Matrix room": "Kunde inte hitta ett matchande Matrix-rum", @@ -378,21 +378,21 @@ "Yesterday": "igår", "Error encountered (%(errorDetail)s).": "Fel påträffat (%(errorDetail)s).", "Low Priority": "Låg prioritet", - "Unable to fetch notification target list": "Det gick inte att hämta aviseringsmållistan", + "Unable to fetch notification target list": "Kunde inte hämta aviseringsmållistan", "Set Password": "Välj lösenord", "Off": "Av", "%(brand)s does not know how to join a room on this network": "%(brand)s kan inte gå med i ett rum på det här nätverket", "Mentions only": "Endast omnämnande", "Failed to remove tag %(tagName)s from room": "Det gick inte att radera etiketten %(tagName)s från rummet", "You can now return to your account after signing out, and sign in on other devices.": "Du kan nu återgå till ditt konto efter att ha loggat ut och logga in på andra enheter.", - "Enable email notifications": "Aktivera epostaviseringar", + "Enable email notifications": "Aktivera e-postaviseringar", "Download this file": "Ladda ner filen", "Failed to change settings": "Det gick inte att spara inställningarna", "View Source": "Visa källa", "Thank you!": "Tack!", "Quote": "Citera", "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!": "Med din nuvarande webbläsare kan appens utseende vara helt fel, och vissa eller alla egenskaper kommer nödvändigtvis inte att fungera. Om du ändå vill försöka så kan du fortsätta, men gör det på egen risk!", - "Checking for an update...": "Letar efter uppdateringar...", + "Checking for an update...": "Letar efter uppdateringar…", "Who can access this room?": "Vilka kan komma åt detta rum?", "Who can read history?": "Vilka kan läsa historik?", "Members only (since the point in time of selecting this option)": "Endast medlemmar (från tidpunkten för när denna inställning valdes)", @@ -426,7 +426,7 @@ "Unnamed Room": "Namnlöst rum", "Your browser does not support the required cryptography extensions": "Din webbläsare stödjer inte nödvändiga kryptografitillägg", "Invite": "Bjud in", - "Unignore": "Ignorera inte", + "Unignore": "Avignorera", "Ignore": "Ignorera", "Jump to message": "Hoppa till meddelande", "Mention": "Nämn", @@ -535,7 +535,7 @@ "Clear filter": "Töm filter", "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Försökte ladda en viss punkt i det här rummets tidslinje, men du har inte behörighet att visa det aktuella meddelandet.", "Tried to load a specific point in this room's timeline, but was unable to find it.": "Försökte ladda en specifik punkt i det här rummets tidslinje, men kunde inte hitta den.", - "Success": "Slutfört", + "Success": "Framgång", "Unable to remove contact information": "Det gick inte att ta bort kontaktuppgifter", "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "Ett epostmeddelande har skickats till %(emailAddress)s. När du har öppnat länken i det, klicka nedan.", "Please note you are logging into the %(hs)s server, not matrix.org.": "Observera att du loggar in på %(hs)s-servern, inte matrix.org.", @@ -1004,26 +1004,26 @@ "Pin": "Häftstift", "We've sent you an email to verify your address. Please follow the instructions there and then click the button below.": "Vi har skickat ett mail till dig för att verifiera din adress. Följ instruktionerna där och klicka sedan på knappen nedan.", "Email Address": "Epostadress", - "Add an email address to configure email notifications": "Lägg till en epostadress för att konfigurera epostaviseringar", + "Add an email address to configure email notifications": "Lägg till en e-postadress för att konfigurera e-postaviseringar", "Unable to verify phone number.": "Det gick inte att verifiera telefonnumret.", "Verification code": "Verifieringskod", "Phone Number": "Telefonnummer", "Profile picture": "Profilbild", "Display Name": "Visningsnamn", - "Set a new account password...": "Ange ett nytt lösenord för kontot...", - "Email addresses": "Epostadresser", + "Set a new account password...": "Ange ett nytt lösenord för kontot…", + "Email addresses": "E-postadresser", "Phone numbers": "Telefonnummer", "Language and region": "Språk och region", "Theme": "Tema", "Account management": "Kontohantering", "Deactivating your account is a permanent action - be careful!": "Inaktivering av ditt konto är en permanent åtgärd - var försiktig!", "General": "Allmänt", - "Credits": "Tack", + "Credits": "Medverkande", "For help with using %(brand)s, click here.": "För hjälp med att använda %(brand)s, klicka här.", - "For help with using %(brand)s, click here or start a chat with our bot using the button below.": "För hjälp med att använda %(brand)s, klicka här eller starta en chatt med vår bot med knappen nedan.", - "Chat with %(brand)s Bot": "Chatta med %(brand)s Bot", - "Help & About": "Hjälp & Om", - "Bug reporting": "Felrapportering", + "For help with using %(brand)s, click here or start a chat with our bot using the button below.": "För hjälp med att använda %(brand)s, klicka här eller starta en chatt med vår bott med knappen nedan.", + "Chat with %(brand)s Bot": "Chatta med %(brand)s-bott", + "Help & About": "Hjälp och om", + "Bug reporting": "Buggrapportering", "FAQ": "FAQ", "Versions": "Versioner", "Preferences": "Alternativ", @@ -1133,7 +1133,7 @@ "Verify this user by confirming the following emoji appear on their screen.": "Verifiera den här användaren genom att bekräfta att följande emojier visas på deras skärm.", "Verify this user by confirming the following number appears on their screen.": "Verifiera den här användaren genom att bekräfta att följande nummer visas på deras skärm.", "Unable to find a supported verification method.": "Kunde inte hitta en verifieringsmetod som stöds.", - "Delete Backup": "Ta bort säkerhetskopia", + "Delete Backup": "Radera säkerhetskopia", "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.": "Är du säker? Du kommer att förlora dina krypterade meddelanden om dina nycklar inte säkerhetskopieras ordentligt.", "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Krypterade meddelanden är säkrade med totalsträckskryptering. Bara du och mottagaren/na har nycklarna för att läsa dessa meddelanden.", "Ignored users": "Ignorerade användare", @@ -1244,24 +1244,24 @@ "Public Name": "Offentligt namn", "Identity Server URL must be HTTPS": "URL för identitetsserver måste vara HTTPS", "Not a valid Identity Server (status code %(code)s)": "Inte en giltig identitetsserver (statuskod %(code)s)", - "Could not connect to Identity Server": "Det gick inte att ansluta till identitetsserver", - "Checking server": "Kontrollerar server", + "Could not connect to Identity Server": "Det gick inte att ansluta till identitetsservern", + "Checking server": "Kontrollerar servern", "Change identity server": "Byt identitetsserver", - "Disconnect from the identity server and connect to instead?": "Koppla från identitetsservern och ansluta till istället?", + "Disconnect from the identity server and connect to instead?": "Koppla ifrån från identitetsservern och anslut till istället?", "Only continue if you trust the owner of the server.": "Fortsätt endast om du litar på serverns ägare.", - "Disconnect identity server": "Koppla från identitetsserver", - "Disconnect from the identity server ?": "Koppla från identitetsserver ?", - "Disconnect": "Koppla från", - "You are still sharing your personal data on the identity server .": "Du delar fortfarande dina personuppgifter på identitetsserver .", + "Disconnect identity server": "Koppla ifrån identitetsservern", + "Disconnect from the identity server ?": "Koppla ifrån från identitetsservern ?", + "Disconnect": "Koppla ifrån", + "You are still sharing your personal data on the identity server .": "Du delar fortfarande dina personuppgifter på identitetsservern .", "We recommend that you remove your email addresses and phone numbers from the identity server before disconnecting.": "Vi rekommenderar att du tar bort dina e-postadresser och telefonnummer från identitetsservern innan du kopplar från.", - "Disconnect anyway": "Koppla från ändå", + "Disconnect anyway": "Koppla ifrån ändå", "Identity Server (%(server)s)": "Identitetsserver (%(server)s)", - "You are currently using to discover and be discoverable by existing contacts you know. You can change your identity server below.": "Du använder för närvarande för att upptäcka och upptäckas av befintliga kontakter som du känner. Du kan ändra din identitetsserver nedan.", + "You are currently using to discover and be discoverable by existing contacts you know. You can change your identity server below.": "Du använder för närvarande för att upptäcka och upptäckas av befintliga kontakter som du känner. Du kan byta din identitetsserver nedan.", "If you don't want to use to discover and be discoverable by existing contacts you know, enter another identity server below.": "Om du inte vill använda för att upptäcka och upptäckas av befintliga kontakter som du känner, ange en annan identitetsserver nedan.", "Identity Server": "Identitetsserver", "You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one below.": "Du använder för närvarande inte en identitetsserver. Lägg till en nedan om du vill upptäcka och bli upptäckbar av befintliga kontakter som du känner.", - "Disconnecting from your identity server will mean you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Att koppla från din identitetsserver betyder att du inte kan upptäckas av andra användare och att du inte kommer att kunna bjuda in andra via epost eller telefon.", - "Using an identity server is optional. If you choose not to use an identity server, you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Att använda en identitetsserver är valfritt. Om du väljer att inte använda en identitetsserver kan du inte upptäckas av andra användare och inte heller bjuda in andra via epost eller telefon.", + "Disconnecting from your identity server will mean you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Att koppla ifrån din identitetsserver betyder att du inte kan upptäckas av andra användare och att du inte kommer att kunna bjuda in andra via e-post eller telefon.", + "Using an identity server is optional. If you choose not to use an identity server, you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Att använda en identitetsserver är valfritt. Om du väljer att inte använda en identitetsserver kan du inte upptäckas av andra användare och inte heller bjuda in andra via e-post eller telefon.", "Do not use an identity server": "Använd inte en identitetsserver", "Enter a new identity server": "Ange en ny identitetsserver", "Integration Manager": "Integrationshanterare", @@ -1326,9 +1326,9 @@ "Unexpected error resolving homeserver configuration": "Oväntat fel vid inläsning av hemserverkonfiguration", "Unexpected error resolving identity server configuration": "Oväntat fel vid inläsning av identitetsserverkonfiguration", "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)": "Tillåt assistansservern turn.matrix.org för samtal som reserv när din hemserver inte erbjuder en (din IP-adress kommer delas under ett samtal)", - "Unable to load key backup status": "Det går inte att ladda status för nyckelsäkerhetskopiering", - "Restore from Backup": "Återställ från säkerhetskopiering", - "Backing up %(sessionsRemaining)s keys...": "Säkerhetskopierar %(sessionsRemaining)s nycklar...", + "Unable to load key backup status": "Kunde inte ladda status för nyckelsäkerhetskopiering", + "Restore from Backup": "Återställ från säkerhetskopia", + "Backing up %(sessionsRemaining)s keys...": "Säkerhetskopierar %(sessionsRemaining)s nycklar…", "All keys backed up": "Alla nycklar säkerhetskopierade", "Add Email Address": "Lägg till e-postadress", "Add Phone Number": "Lägg till telefonnummer", @@ -1351,13 +1351,13 @@ "Match system theme": "Matcha systemtema", "Decline (%(counter)s)": "Avvisa (%(counter)s)", "not found": "hittades inte", - "Connecting to integration manager...": "Ansluter till integrationshanterare...", - "Cannot connect to integration manager": "Det går inte att ansluta till integrationshanterare", + "Connecting to integration manager...": "Ansluter till integrationshanterare…", + "Cannot connect to integration manager": "Kan inte ansluta till integrationshanteraren", "The integration manager is offline or it cannot reach your homeserver.": "Integrationshanteraren är offline eller kan inte nå din hemserver.", "Use an Integration Manager (%(serverName)s) to manage bots, widgets, and sticker packs.": "Använd en integrationshanterare (%(serverName)s) för att hantera bottar, widgets och dekalpaket.", "Use an Integration Manager to manage bots, widgets, and sticker packs.": "Använd en integrationshanterare för att hantera bottar, widgets och dekalpaket.", "Manage integrations": "Hantera integrationer", - "Integration Managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Integrationshanterare får konfigurationsdata och kan ändra widgetar, skicka ruminbjudningar och ställa in behörighetsnivåer via ditt konto.", + "Integration Managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Integrationshanterare får konfigurationsdata och kan ändra widgetar, skicka rumsinbjudningar och ställa in behörighetsnivåer å dina vägnar.", "Close preview": "Stäng förhandsvisning", "Room %(name)s": "Rum %(name)s", "Recent rooms": "Senaste rummen", @@ -1695,5 +1695,139 @@ "Workspace: %(networkName)s": "Arbetsyta: %(networkName)s", "Channel: %(channelName)s": "Kanal: %(channelName)s", "Show less": "Visa mindre", - "Changing password will currently reset any end-to-end encryption keys on all sessions, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Att byta lösenord återställer just nu alla krypteringsnycklar på alla sessioner, vilket gör krypterad chatthistorik oläslig om du inte först exporterar dina rumsnycklar och sedan importerar dem igen efteråt. Detta kommer att förbättras i framtiden." + "Changing password will currently reset any end-to-end encryption keys on all sessions, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Att byta lösenord återställer just nu alla krypteringsnycklar på alla sessioner, vilket gör krypterad chatthistorik oläslig om du inte först exporterar dina rumsnycklar och sedan importerar dem igen efteråt. Detta kommer att förbättras i framtiden.", + "Your homeserver does not support cross-signing.": "Din hemserver stöder inte korssignering.", + "Cross-signing and secret storage are ready for use.": "Korssignering och hemlig lagring är klara att använda.", + "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "Korssignering är klar att använda, men hemlig lagring används just nu inte för att säkerhetskopiera dina nycklar.", + "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.": "Ditt konto har en korssigneringsidentitet i hemlig lagring, men den är inte betrodd av den här sessionen än.", + "Cross-signing and secret storage are not yet set up.": "Korssignering och hemlig lagring har inte blivit uppsatta än.", + "Reset cross-signing and secret storage": "Återställ korssignering och hemlig lagring", + "Bootstrap cross-signing and secret storage": "Sätt upp korssignering och hemlig lagring", + "well formed": "välformaterad", + "unexpected type": "oväntad typ", + "Cross-signing public keys:": "Publika nycklar för korssignering:", + "in memory": "i minne", + "Cross-signing private keys:": "Privata nycklar för korssignering:", + "in secret storage": "i hemlig lagring", + "Master private key:": "Privat huvudnyckel:", + "cached locally": "cachad lokalt", + "not found locally": "inte hittad lokalt", + "Self signing private key:": "Privat nyckel för självsignering:", + "User signing private key:": "Privat nyckel för användarsignering:", + "Session backup key:": "Sessionssäkerhetskopieringsnyckel:", + "Secret storage public key:": "Publik nyckel för hemlig lagring:", + "in account data": "i kontodata", + "Homeserver feature support:": "Hemserverns funktionsstöd:", + "exists": "existerar", + "Your homeserver does not support session management.": "Din hemservers stöder inte sessionshantering.", + "Unable to load session list": "Kunde inte ladda sessionslistan", + "Confirm deleting these sessions by using Single Sign On to prove your identity.|other": "Bekräfta radering av dessa sessioner genom att använda single sign-on för att bekräfta din identitet.", + "Confirm deleting these sessions by using Single Sign On to prove your identity.|one": "Bekräfta radering av denna session genom att använda single sign-on för att bekräfta din identitet.", + "Confirm deleting these sessions": "Bekräfta radering av dessa sessioner", + "Click the button below to confirm deleting these sessions.|other": "Klicka på knappen nedan för att bekräfta radering av dessa sessioner.", + "Click the button below to confirm deleting these sessions.|one": "Klicka på knappen nedan för att bekräfta radering av denna session.", + "Delete sessions|other": "Radera sessioner", + "Delete sessions|one": "Radera session", + "Delete %(count)s sessions|other": "Radera %(count)s sessioner", + "Delete %(count)s sessions|one": "Radera %(count)s session", + "Individually verify each session used by a user to mark it as trusted, not trusting cross-signed devices.": "Verifiera individuellt varje session som används av en användare för att markera den som betrodd, och lita inte på korssignerade enheter.", + "Securely cache encrypted messages locally for them to appear in search results, using ": "Cacha krypterade meddelanden säkert lokalt för att de ska visas i sökresultat, med hjälp av ", + " to store messages from ": " för att lagra meddelanden från ", + "rooms.": "rum.", + "Manage": "Hantera", + "Securely cache encrypted messages locally for them to appear in search results.": "Cachar krypterade meddelanden säkert lokalt för att de ska visas i sökresultat.", + "Enable": "Aktivera", + "%(brand)s is missing some components required for securely caching encrypted messages locally. If you'd like to experiment with this feature, build a custom %(brand)s Desktop with search components added.": "%(brand)s saknar vissa komponenter som krävs som krävs för att säkert cacha krypterade meddelanden lokalt. Om du vill experimentera med den här funktionen, bygg en anpassad %(brand)s Skrivbord med sökkomponenter tillagda.", + "%(brand)s can't securely cache encrypted messages locally while running in a web browser. Use %(brand)s Desktop for encrypted messages to appear in search results.": "%(brand)s kan inte säkert cacha krypterade meddelanden lokalt när den kör i en webbläsare. Använd %(brand)s Skrivbord för att krypterade meddelanden ska visas i sökresultaten.", + "This session is backing up your keys. ": "Den här sessionen säkerhetskopierar dina nycklar. ", + "This session is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.": "Den här servern säkerhetskopierar inte dina nycklar, men du har en existerande säkerhetskopia du kan återställa ifrån och lägga till till i framtiden.", + "Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.": "Anslut den här sessionen till nyckelsäkerhetskopiering innan du loggar ut för att undvika att du blir av med nycklar som kanske bara finns på den här sessionen.", + "Connect this session to Key Backup": "Anslut den här sessionen till nyckelsäkerhetskopiering", + "not stored": "inte lagrad", + "Backup has a valid signature from this user": "Säkerhetskopian har en giltig signatur från den här användaren", + "Backup has a invalid signature from this user": "Säkerhetskopian har en ogiltig signatur från den här användaren", + "Backup has a signature from unknown user with ID %(deviceId)s": "Säkerhetskopian har en signatur från en okänd användare med ID %(deviceId)s", + "Backup has a signature from unknown session with ID %(deviceId)s": "Säkerhetskopian har en signatur från en okänd session med ID %(deviceId)s", + "Backup has a valid signature from this session": "Säkerhetskopian har en giltig signatur från den här sessionen", + "Backup has an invalid signature from this session": "Säkerhetskopian har en ogiltig signatur från den här sessionen", + "Backup has a valid signature from verified session ": "Säkerhetskopian har en giltig signatur från den verifierade sessionen ", + "Backup has a valid signature from unverified session ": "Säkerhetskopian har en giltig signatur från den overifierade sessionen ", + "Backup has an invalid signature from verified session ": "Säkerhetskopian har en ogiltig signatur från den verifierade sessionen ", + "Backup has an invalid signature from unverified session ": "Säkerhetskopian har en ogiltig signatur från den overifierade sessionen ", + "Backup is not signed by any of your sessions": "Säkerhetskopian är inte signerad av någon av dina sessioner", + "This backup is trusted because it has been restored on this session": "Den här säkerhetskopian är betrodd för att den har återställts på den här sessionen", + "Backup version: ": "Säkerhetskopiaversion: ", + "Algorithm: ": "Algoritm: ", + "Backup key stored: ": "Säkerhetskopianyckel lagrad: ", + "Your keys are not being backed up from this session.": "Dina nycklar säkerhetskopieras inte från den här sessionen.", + "Back up your keys before signing out to avoid losing them.": "Säkerhetskopiera dina nycklar innan du loggar ut för att undvika att du blir av med dem.", + "Start using Key Backup": "Börja använda nyckelsäkerhetskopiering", + "Clear notifications": "Rensa aviseringar", + "There are advanced notifications which are not shown here.": "Det finns avancerade aviseringar som inte visas här.", + "You might have configured them in a client other than %(brand)s. You cannot tune them in %(brand)s but they still apply.": "Du kanske har konfigurerat dem i en annan klient än %(brand)s. Du kan inte ändra dem i %(brand)s men de används ändå.", + "Enable desktop notifications for this session": "Aktivera skrivbordsaviseringar för den här sessionen", + "Enable audible notifications for this session": "Aktivera ljudaviseringar för den här sessionen", + "Upgrade to your own domain": "Uppgradera till din egen domän", + "Terms of service not accepted or the identity server is invalid.": "Användarvillkoren accepterades inte eller identitetsservern är inte giltig.", + "The identity server you have chosen does not have any terms of service.": "Identitetsservern du har valt har inga användarvillkor.", + "You should remove your personal data from identity server before disconnecting. Unfortunately, identity server is currently offline or cannot be reached.": "Du bör ta bort din personliga information från identitetsservern innan du kopplar ifrån. Tyvärr är identitetsservern för närvarande offline eller kan inte nås.", + "You should:": "Du bör:", + "check your browser plugins for anything that might block the identity server (such as Privacy Badger)": "Kolla dina webbläsartillägg efter någonting som kanske blockerar identitetsservern (t.ex. Privacy Badger)", + "contact the administrators of identity server ": "kontakta administratören för identitetsservern ", + "wait and try again later": "vänta och försöka igen senare", + "New version available. Update now.": "Ny version tillgänglig. Uppdatera nu.", + "Hey you. You're the best!": "Hallå där. Du är bäst!", + "Size must be a number": "Storleken måste vara ett nummer", + "Custom font size can only be between %(min)s pt and %(max)s pt": "Anpassad teckenstorlek kan bara vara mellan %(min)s pt och %(max)s pt", + "Use between %(min)s pt and %(max)s pt": "Använd mellan %(min)s pt och %(max)s pt", + "Invalid theme schema.": "Ogiltigt temaschema.", + "Error downloading theme information.": "Fel vid nedladdning av temainformation.", + "Theme added!": "Tema tillagt!", + "Custom theme URL": "Anpassad tema-URL", + "Add theme": "Lägg till tema", + "Message layout": "Meddelandearrangemang", + "Compact": "Kompakt", + "Modern": "Modernt", + "Set the name of a font installed on your system & %(brand)s will attempt to use it.": "Sätt namnet för ett teckensnitt installerat på ditt system så kommer %(brand)s att försöka använda det.", + "Customise your appearance": "Anpassa ditt utseende", + "Appearance Settings only affect this %(brand)s session.": "Utseende inställningar påverkar bara den här %(brand)s-sessionen.", + "Your password was successfully changed. You will not receive push notifications on other sessions until you log back in to them": "Ditt lösenord ändrades framgångsrikt. Du kommer inte motta pushnotiser på andra sessioner till du loggar in på dem igen", + "Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.": "Samtyck till identitetsserverns (%(serverName)s) användarvillkor för att låta dig själv vara upptäckbar med e-postadress eller telefonnummer.", + "Clear cache and reload": "Rensa cache och ladda om", + "To report a Matrix-related security issue, please read the Matrix.org Security Disclosure Policy.": "För att rapportera ett Matrix-relaterat säkerhetsproblem, vänligen läs Matrix.orgs riktlinjer för säkerhetspublicering.", + "Keyboard Shortcuts": "Tangentbordsgenvägar", + "Customise your experience with experimental labs features. Learn more.": "Anpassa din upplevelse med experimentella funktioner. Lär dig mer.", + "Ignored/Blocked": "Ignorerade/blockerade", + "Error adding ignored user/server": "Fel vid tilläggning av användare/server", + "Something went wrong. Please try again or view your console for hints.": "Någonting gick fel. Vänligen försök igen eller kolla i din konsol efter ledtrådar.", + "Error subscribing to list": "Fel vid prenumeration på listan", + "Please verify the room ID or address and try again.": "Vänligen verifiera rummets ID eller adress och försök igen.", + "Error removing ignored user/server": "Fel vid borttagning av ignorerad användare/server", + "Error unsubscribing from list": "Fel vid avprenumeration från listan", + "Please try again or view your console for hints.": "Vänligen försök igen eller kolla din konsol efter ledtrådar.", + "None": "Ingen", + "Ban list rules - %(roomName)s": "Bannlistregler - %(roomName)s", + "Server rules": "Serverregler", + "User rules": "Användarregler", + "You have not ignored anyone.": "Du har inte ignorerat någon.", + "You are currently ignoring:": "Du ignorerar just nu:", + "You are not subscribed to any lists": "Du prenumererar inte på några listor", + "Unsubscribe": "Avprenumerera", + "View rules": "Visa regler", + "You are currently subscribed to:": "Du prenumerera just nu på:", + "⚠ These settings are meant for advanced users.": "⚠ Dessa inställningar är till för avancerade användare.", + "Add users and servers you want to ignore here. Use asterisks to have %(brand)s match any characters. For example, @bot:* would ignore all users that have the name 'bot' on any server.": "Lägg till användare och servrar du vill ignorera här. Använd asterisker för att få %(brand)s att matchar vilka tecken som helt. Till exempel, @bot:* kommer att ignorera alla användare med namnet 'bot' på vilken server som helst.", + "Ignoring people is done through ban lists which contain rules for who to ban. Subscribing to a ban list means the users/servers blocked by that list will be hidden from you.": "Ignorering av användare görs genom bannlistor som innehåller regler för vilka som bannas. Att prenumerera på en bannlista betyder att användare/servrar blockerade av den listan kommer att döljas för dig.", + "Personal ban list": "Personlig bannlista", + "Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named 'My Ban List' - stay in this room to keep the ban list in effect.": "Din personliga bannlista innehåller alla användare/servrar du personligen inte vill se meddelanden ifrån. Efter att du ignorerar din första användare/server så kommer ett nytt rom att dyka upp i din rumslista med namnet 'Min bannlista' - stanna i det här rummet för att hålla bannlistan verksam.", + "Server or user ID to ignore": "Server- eller användar-ID att ignorera", + "eg: @bot:* or example.org": "t.ex.: @bot:* eller example.org", + "Subscribed lists": "Prenumererade listor", + "Subscribing to a ban list will cause you to join it!": "Att prenumerera till en bannlista kommer att få dig att gå med i den!", + "If this isn't what you want, please use a different tool to ignore users.": "Om det här inte är det du vill, använd ett annat verktyg för att ignorera användare.", + "Room ID or address of ban list": "Rums-ID eller adress för bannlista", + "Subscribe": "Prenumerera", + "Show tray icon and minimize window to it on close": "Visa systembricksikonen och minimera fönstret till den vid stängning", + "Read Marker lifetime (ms)": "Läsmarkörens livstid (ms)", + "Read Marker off-screen lifetime (ms)": "Läsmarkörens livstid utanför skärmen (ms)" } From b312eb88e9fcd7bb433f1f8319fb34faca1a1ddc Mon Sep 17 00:00:00 2001 From: purjolini Date: Sat, 29 Aug 2020 13:56:55 +0000 Subject: [PATCH 040/286] Translated using Weblate (Swedish) Currently translated at 74.3% (1751 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 16be70f1c8..7406ef8d95 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -1829,5 +1829,6 @@ "Subscribe": "Prenumerera", "Show tray icon and minimize window to it on close": "Visa systembricksikonen och minimera fönstret till den vid stängning", "Read Marker lifetime (ms)": "Läsmarkörens livstid (ms)", - "Read Marker off-screen lifetime (ms)": "Läsmarkörens livstid utanför skärmen (ms)" + "Read Marker off-screen lifetime (ms)": "Läsmarkörens livstid utanför skärmen (ms)", + "Session ID:": " Sessions ID:" } From 437a0b52e4db361c201eb0175606f61b63a09fc2 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Sat, 29 Aug 2020 13:57:14 +0000 Subject: [PATCH 041/286] Translated using Weblate (Swedish) Currently translated at 74.3% (1752 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 7406ef8d95..28374ff5b4 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -1830,5 +1830,5 @@ "Show tray icon and minimize window to it on close": "Visa systembricksikonen och minimera fönstret till den vid stängning", "Read Marker lifetime (ms)": "Läsmarkörens livstid (ms)", "Read Marker off-screen lifetime (ms)": "Läsmarkörens livstid utanför skärmen (ms)", - "Session ID:": " Sessions ID:" + "Session ID:": "Sessions-ID:" } From e1f0361bf26619ec0fcabb63131089196b2d12b2 Mon Sep 17 00:00:00 2001 From: purjolini Date: Sat, 29 Aug 2020 13:57:17 +0000 Subject: [PATCH 042/286] Translated using Weblate (Swedish) Currently translated at 74.3% (1752 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 28374ff5b4..e145f57aed 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -1830,5 +1830,6 @@ "Show tray icon and minimize window to it on close": "Visa systembricksikonen och minimera fönstret till den vid stängning", "Read Marker lifetime (ms)": "Läsmarkörens livstid (ms)", "Read Marker off-screen lifetime (ms)": "Läsmarkörens livstid utanför skärmen (ms)", - "Session ID:": "Sessions-ID:" + "Session ID:": "Sessions-ID:", + "Session key:": "Sessions nyckel:" } From 8cae2fa0aafb22b44bdcb75d0e5757c30eab06da Mon Sep 17 00:00:00 2001 From: dkanada Date: Sun, 30 Aug 2020 15:17:21 +0900 Subject: [PATCH 043/286] update translations with new command string --- src/i18n/strings/en_EN.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 442f07499c..bf8118f2e5 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -149,6 +149,7 @@ "Command error": "Command error", "Usage": "Usage", "Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Prepends ¯\\_(ツ)_/¯ to a plain-text message", + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message", "Sends a message as plain text, without interpreting it as markdown": "Sends a message as plain text, without interpreting it as markdown", "Sends a message as html, without interpreting it as markdown": "Sends a message as html, without interpreting it as markdown", "Searches DuckDuckGo for results": "Searches DuckDuckGo for results", From 47e834679d277f5359fe214f8c408c8d38d0d6c7 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Sat, 29 Aug 2020 13:57:21 +0000 Subject: [PATCH 044/286] Translated using Weblate (Swedish) Currently translated at 86.1% (2030 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 530 ++++++++++++++++++++++++++++----------- 1 file changed, 387 insertions(+), 143 deletions(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index e145f57aed..fff7f90b82 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -7,7 +7,7 @@ "No Microphones detected": "Ingen mikrofon hittades", "No Webcams detected": "Ingen webbkamera hittades", "No media permissions": "Inga mediebehörigheter", - "You may need to manually permit %(brand)s to access your microphone/webcam": "Du måste manuellt tillåta %(brand)s att komma åt din mikrofon/kamera", + "You may need to manually permit %(brand)s to access your microphone/webcam": "Du behöver manuellt tillåta %(brand)s att komma åt din mikrofon/kamera", "Default Device": "Standardenhet", "Microphone": "Mikrofon", "Camera": "Kamera", @@ -15,8 +15,8 @@ "Always show message timestamps": "Visa alltid tidsstämplar för meddelanden", "Authentication": "Autentisering", "%(items)s and %(lastItem)s": "%(items)s och %(lastItem)s", - "and %(count)s others...|other": "och %(count)s andra...", - "and %(count)s others...|one": "och en annan...", + "and %(count)s others...|other": "och %(count)s andra…", + "and %(count)s others...|one": "och en annan…", "A new password must be entered.": "Ett nytt lösenord måste anges.", "%(senderName)s answered the call.": "%(senderName)s svarade på samtalet.", "Anyone who knows the room's link, including guests": "Alla som har rummets adress, inklusive gäster", @@ -56,37 +56,37 @@ "Custom level": "Anpassad nivå", "/ddg is not a command": "/ddg är inte ett kommando", "Deactivate Account": "Inaktivera konto", - "Decrypt %(text)s": "Dekryptera %(text)s", + "Decrypt %(text)s": "Avkryptera %(text)s", "Deops user with given id": "Degraderar användaren med givet ID", "Default": "Standard", "Disinvite": "Häv inbjudan", "Displays action": "Visar åtgärd", "Download %(text)s": "Ladda ner %(text)s", "Email": "Epost", - "Email address": "Epostadress", + "Email address": "E-postadress", "Emoji": "Emoji", "%(senderName)s ended the call.": "%(senderName)s avslutade samtalet.", "Error": "Fel", - "Error decrypting attachment": "Det gick inte att dekryptera bilagan", + "Error decrypting attachment": "Fel vid avkryptering av bilagan", "Existing Call": "Existerande samtal", "Export": "Exportera", "Export E2E room keys": "Exportera krypteringsrumsnycklar", - "Failed to ban user": "Det gick inte att banna användaren", + "Failed to ban user": "Misslyckades att banna användaren", "Failed to change password. Is your password correct?": "Misslyckades att byta lösenord. Är lösenordet rätt?", - "Failed to change power level": "Det gick inte att ändra behörighetsnivå", + "Failed to change power level": "Misslyckades att ändra behörighetsnivå", "Failed to forget room %(errCode)s": "Det gick inte att glömma bort rummet %(errCode)s", - "Failed to join room": "Det gick inte att gå med i rummet", - "Failed to kick": "Det gick inte att kicka", + "Failed to join room": "Misslyckades att gå med i rummet", + "Failed to kick": "Misslyckades att kicka", "Failed to leave room": "Det gick inte att lämna rummet", "Failed to load timeline position": "Det gick inte att hämta positionen på tidslinjen", - "Failed to mute user": "Det gick inte att tysta användaren", + "Failed to mute user": "Misslyckades att tysta användaren", "Failed to reject invite": "Det gick inte att avböja inbjudan", "Failed to reject invitation": "Det gick inte att avböja inbjudan", "Failed to send email": "Det gick inte att skicka epost", - "Failed to send request.": "Det gick inte att sända begäran.", + "Failed to send request.": "Misslyckades att sända begäran.", "Failed to set display name": "Misslyckades att ange visningsnamn", - "Failed to unban": "Det gick inte att avbanna", - "Failed to verify email address: make sure you clicked the link in the email": "Det gick inte att bekräfta e-postadressen: set till att du klickade på länken i e-postmeddelandet", + "Failed to unban": "Misslyckades att avbanna", + "Failed to verify email address: make sure you clicked the link in the email": "Misslyckades att bekräfta e-postadressen: set till att du klickade på länken i e-postmeddelandet", "Favourite": "Favorit", "Accept": "Godkänn", "Access Token:": "Åtkomsttoken:", @@ -103,14 +103,14 @@ "Error: Problem communicating with the given homeserver.": "Fel: Det gick inte att kommunicera med den angivna hemservern.", "Failed to fetch avatar URL": "Det gick inte att hämta avatar-URL", "Failed to upload profile picture!": "Misslyckades att ladda upp profilbild!", - "Failure to create room": "Det gick inte att skapa rummet", + "Failure to create room": "Misslyckades att skapa rummet", "Favourites": "Favoriter", "Fill screen": "Fyll skärmen", "Filter room members": "Filtrera rumsmedlemmar", "Forget room": "Glöm bort rum", "For security, this session has been signed out. Please sign in again.": "Av säkerhetsskäl har den här sessionen loggats ut. Vänligen logga in igen.", "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s från %(fromPowerLevel)s till %(toPowerLevel)s", - "Guests cannot join this room even if explicitly invited.": "Gäster kan inte gå med i det här rummet fastän de är uttryckligen inbjudna.", + "Guests cannot join this room even if explicitly invited.": "Gäster kan inte gå med i det här rummet även om de är uttryckligen inbjudna.", "Hangup": "Lägg på", "Historical": "Historiska", "Home": "Hem", @@ -124,7 +124,7 @@ "Incoming voice call from %(name)s": "Inkommande röstsamtal från %(name)s", "Incorrect username and/or password.": "Fel användarnamn och/eller lösenord.", "Incorrect verification code": "Fel verifieringskod", - "Invalid Email Address": "Ogiltig epostadress", + "Invalid Email Address": "Ogiltig e-postadress", "Invalid file%(extra)s": "Felaktig fil%(extra)s", "%(senderName)s invited %(targetName)s.": "%(senderName)s bjöd in %(targetName)s.", "Invited": "Inbjuden", @@ -134,7 +134,7 @@ "Join as voice or video.": "Gå med som röst eller video.", "Join Room": "Gå med i rum", "%(targetName)s joined the room.": "%(targetName)s gick med i rummet.", - "Jump to first unread message.": "Hoppa till första olästa meddelande.", + "Jump to first unread message.": "Hoppa till första olästa meddelandet.", "%(senderName)s kicked %(targetName)s.": "%(senderName)s kickade %(targetName)s.", "Kick": "Kicka", "Kicks user with given id": "Kickar användaren med givet ID", @@ -156,7 +156,7 @@ "Name": "Namn", "New passwords don't match": "De nya lösenorden matchar inte", "New passwords must match each other.": "De nya lösenorden måste vara de samma.", - "not specified": "inte specifierad", + "not specified": "inte specificerad", "Notifications": "Aviseringar", "(not supported by this browser)": "(stöds inte av webbläsaren)", "": "", @@ -207,7 +207,7 @@ "Server error": "Serverfel", "Server may be unavailable, overloaded, or search timed out :(": "Servern kan vara otillgänglig, överbelastad, eller så tog sökningen för lång tid :(", "Server may be unavailable, overloaded, or you hit a bug.": "Servern kan vara otillgänglig eller överbelastad, eller så stötte du på en bugg.", - "Server unavailable, overloaded, or something else went wrong.": "Servern är otillgänglig, överbelastad, eller så gick något annat fel.", + "Server unavailable, overloaded, or something else went wrong.": "Servern är otillgänglig eller överbelastad, eller så gick något annat fel.", "Session ID": "Sessions-ID", "%(senderName)s set a profile picture.": "%(senderName)s satte en profilbild.", "%(senderName)s set their display name to %(displayName)s.": "%(senderName)s bytte sitt visningnamn till %(displayName)s.", @@ -229,8 +229,8 @@ "unknown error code": "okänd felkod", "Add a widget": "Lägg till en widget", "Allow": "Tillåt", - "Cannot add any more widgets": "Det går inte att lägga till fler widgets", - "Delete widget": "Ta bort widget", + "Cannot add any more widgets": "Kan inte lägga till fler widgets", + "Delete widget": "Radera widget", "Define the power level of a user": "Definiera behörighetsnivå för en användare", "Edit": "Ändra", "Enable automatic language detection for syntax highlighting": "Aktivera automatisk språkdetektering för syntaxmarkering", @@ -238,7 +238,7 @@ "AM": "FM", "PM": "EM", "Submit": "Lämna in", - "The maximum permitted number of widgets have already been added to this room.": "Den största tillåtna mängden widgetar har redan tillsats till rummet.", + "The maximum permitted number of widgets have already been added to this room.": "Den största tillåtna mängden widgetar har redan tillagts till rummet.", "The phone number entered looks invalid": "Det angivna telefonnumret är ogiltigt", "This email address is already in use": "Den här e-postadressen används redan", "This email address was not found": "Den här e-postadressen finns inte", @@ -246,7 +246,7 @@ "Online": "Online", "Unnamed room": "Namnlöst rum", "World readable": "Alla kan läsa", - "Guests can join": "Gäster kan gå med i rummet", + "Guests can join": "Gäster kan gå med", "No rooms to show": "Inga fler rum att visa", "This phone number is already in use": "Detta telefonnummer används redan", "The version of %(brand)s": "Version av %(brand)s", @@ -299,7 +299,7 @@ "Changelog": "Ändringslogg", "Waiting for response from server": "Väntar på svar från servern", "Leave": "Lämna", - "Uploaded on %(date)s by %(user)s": "%(user)s laddade upp %(date)s", + "Uploaded on %(date)s by %(user)s": "Uppladdad av %(user)s vid %(date)s", "Advanced notification settings": "Avancerade aviseringsinställningar", "Forget": "Glöm bort", "You cannot delete this image. (%(code)s)": "Du kan inte radera den här bilden. (%(code)s)", @@ -387,7 +387,7 @@ "You can now return to your account after signing out, and sign in on other devices.": "Du kan nu återgå till ditt konto efter att ha loggat ut och logga in på andra enheter.", "Enable email notifications": "Aktivera e-postaviseringar", "Download this file": "Ladda ner filen", - "Failed to change settings": "Det gick inte att spara inställningarna", + "Failed to change settings": "Misslyckades att spara inställningarna", "View Source": "Visa källa", "Thank you!": "Tack!", "Quote": "Citera", @@ -410,8 +410,8 @@ "You cannot place VoIP calls in this browser.": "Du kan inte ringa VoIP-samtal i den här webbläsaren.", "Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Din e-postadress verkar inte vara kopplad till något Matrix-ID på den här hemservern.", "Restricted": "Begränsad", - "Failed to invite the following users to the %(roomName)s room:": "Det gick inte att bjuda in följande användare till rummet %(roomName)s:", - "Unable to create widget.": "Det gick inte att skapa widgeten.", + "Failed to invite the following users to the %(roomName)s room:": "Misslyckades att bjuda in följande användare till rummet %(roomName)s:", + "Unable to create widget.": "Kunde inte skapa widgeten.", "Ignored user": "Ignorerad användare", "You are now ignoring %(userId)s": "Du ignorerar nu %(userId)s", "Unignored user": "Avignorerad användare", @@ -436,8 +436,8 @@ "Send an encrypted reply…": "Skicka ett krypterat svar…", "Send an encrypted message…": "Skicka ett krypterat meddelande…", "You do not have permission to post to this room": "Du har inte behörighet att posta till detta rum", - "Loading...": "Laddar...", - "%(duration)ss": "%(duration)s", + "Loading...": "Laddar…", + "%(duration)ss": "%(duration)ss", "%(duration)sm": "%(duration)sm", "%(duration)sh": "%(duration)sh", "%(duration)sd": "%(duration)sd", @@ -450,7 +450,7 @@ "(~%(count)s results)|other": "(~%(count)s resultat)", "(~%(count)s results)|one": "(~%(count)s resultat)", "Upload avatar": "Ladda upp avatar", - "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (nivå %(powerLevelNumber)s)", + "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (behörighet %(powerLevelNumber)s)", "Unknown Address": "Okänd adress", "%(nameList)s %(transitionList)s": "%(nameList)s %(transitionList)s", "%(severalUsers)sjoined %(count)s times|other": "%(severalUsers)sgick med %(count)s gånger", @@ -465,17 +465,17 @@ "%(severalUsers)sjoined and left %(count)s times|one": "%(severalUsers)sgick med och lämnade", "%(oneUser)sjoined and left %(count)s times|other": "%(oneUser)sgick med och lämnade %(count)s gånger", "%(oneUser)sjoined and left %(count)s times|one": "%(oneUser)sgick med och lämnade", - "And %(count)s more...|other": "Och %(count)s till...", + "And %(count)s more...|other": "Och %(count)s till…", "ex. @bob:example.com": "t.ex. @kalle:exempel.com", "Add User": "Lägg till användare", "Matrix ID": "Matrix-ID", - "Matrix Room ID": "Matrix-rums-ID", - "email address": "epostadress", + "Matrix Room ID": "Matrixrums-ID", + "email address": "e-postadress", "Try using one of the following valid address types: %(validTypesList)s.": "Prova att använda någon av följande giltiga adresstyper: %(validTypesList)s.", "You have entered an invalid address.": "Du har angett en ogiltig adress.", - "Preparing to send logs": "Förbereder att skicka loggar", + "Preparing to send logs": "Förbereder sändning av loggar", "Logs sent": "Loggar skickade", - "Failed to send logs: ": "Det gick inte att skicka loggar: ", + "Failed to send logs: ": "Misslyckades att skicka loggar: ", "Submit debug logs": "Skicka felsökningsloggar", "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.": "Felsökningsloggar innehåller användningsdata för applikationen inklusive ditt användarnamn, ID:n eller alias för de rum och grupper du har besökt och användarnamn för andra användare. De innehåller inte meddelanden.", "An email has been sent to %(emailAddress)s": "Ett epostmeddelande har skickats till %(emailAddress)s", @@ -490,10 +490,10 @@ "Uploading %(filename)s and %(count)s others|other": "Laddar upp %(filename)s och %(count)s andra", "Uploading %(filename)s and %(count)s others|zero": "Laddar upp %(filename)s", "Uploading %(filename)s and %(count)s others|one": "Laddar upp %(filename)s och %(count)s annan", - "This doesn't appear to be a valid email address": "Det här verkar inte vara en giltig epostadress", + "This doesn't appear to be a valid email address": "Det här verkar inte vara en giltig e-postadress", "Verification Pending": "Avvaktar verifiering", - "Unable to add email address": "Det gick inte att lägga till epostadress", - "Unable to verify email address.": "Det gick inte att verifiera epostadressen.", + "Unable to add email address": "Kunde inte lägga till e-postadress", + "Unable to verify email address.": "Kunde inte verifiera e-postadressen.", "Skip": "Hoppa över", "Username not available": "Användarnamn inte tillgängligt", "Username invalid: %(errMessage)s": "Ogiltigt användarnamn: %(errMessage)s", @@ -536,18 +536,18 @@ "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Försökte ladda en viss punkt i det här rummets tidslinje, men du har inte behörighet att visa det aktuella meddelandet.", "Tried to load a specific point in this room's timeline, but was unable to find it.": "Försökte ladda en specifik punkt i det här rummets tidslinje, men kunde inte hitta den.", "Success": "Framgång", - "Unable to remove contact information": "Det gick inte att ta bort kontaktuppgifter", + "Unable to remove contact information": "Kunde inte ta bort kontaktuppgifter", "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "Ett epostmeddelande har skickats till %(emailAddress)s. När du har öppnat länken i det, klicka nedan.", "Please note you are logging into the %(hs)s server, not matrix.org.": "Observera att du loggar in på %(hs)s-servern, inte matrix.org.", "This homeserver doesn't offer any login flows which are supported by this client.": "Denna hemserver erbjuder inga inloggningsflöden som stöds av den här klienten.", "Upload new:": "Ladda upp ny:", "Copied!": "Kopierat!", - "Failed to copy": "Det gick inte att kopiera", - "Delete Widget": "Ta bort widget", - "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Widget tas bort för alla användare i rummet. Är du säker på att du vill ta bort den?", + "Failed to copy": "Misslyckades att kopiera", + "Delete Widget": "Radera widget", + "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Att radera en widget tar bort den för alla användare i rummet. Är du säker på att du vill radera den?", "Minimize apps": "Minimera appar", - "Failed to invite the following users to %(groupId)s:": "Det gick inte att bjuda in följande användare till %(groupId)s:", - "Failed to invite users to %(groupId)s": "Det gick inte att bjuda in användare till %(groupId)s", + "Failed to invite the following users to %(groupId)s:": "Misslyckades att bjuda in följande användare till %(groupId)s:", + "Failed to invite users to %(groupId)s": "Misslyckades att bjuda in användare till %(groupId)s", "This room is not public. You will not be able to rejoin without an invite.": "Detta rum är inte offentligt. Du kommer inte kunna gå med igen utan en inbjudan.", "Ignores a user, hiding their messages from you": "Ignorerar en användare och döljer dess meddelanden för dig", "Stops ignoring a user, showing their messages going forward": "Slutar ignorera en användare och visar dess meddelanden framöver", @@ -560,9 +560,9 @@ "File to import": "Fil att importera", "Which officially provided instance you are using, if any": "Vilken officiellt tillhandahållen instans du använder, om någon", "(unknown failure: %(reason)s)": "(okänt fel: %(reason)s)", - "(could not connect media)": "(det gick inte ansluta media)", + "(could not connect media)": "(kunde inte ansluta media)", " (unsupported)": " (stöds ej)", - "Drop file here to upload": "Släpp fil här för att ladda upp", + "Drop file here to upload": "Släpp en fil här för att ladda upp", "Ongoing conference call%(supportedText)s.": "Pågående gruppsamtal%(supportedText)s.", "%(senderName)s sent an image": "%(senderName)s skickade en bild", "%(senderName)s sent a video": "%(senderName)s skickade en video", @@ -574,8 +574,8 @@ "Banned by %(displayName)s": "Bannad av %(displayName)s", "Muted Users": "Dämpade användare", "This room is not accessible by remote Matrix servers": "Detta rum är inte tillgängligt för externa Matrix-servrar", - "Unable to load event that was replied to, it either does not exist or you do not have permission to view it.": "Det gick inte att ladda händelsen som svarades på, antingen finns den inte eller så har du inte behörighet att se den.", - "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.": "Är du säker på att du vill ta bort den här händelsen? Observera att om du tar bort en rumsnamns- eller ämnesändring kan det ångra ändringen.", + "Unable to load event that was replied to, it either does not exist or you do not have permission to view it.": "Kunde inte ladda händelsen som svarades på, antingen så finns den inte eller så har du inte behörighet att se den.", + "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.": "Är du säker på att du vill ta bort (radera) den här händelsen? Observera att om du tar bort en rumsnamns- eller ämnesändring kan det ångra ändringen.", "Send Custom Event": "Skicka anpassad händelse", "You must specify an event type!": "Du måste ange en händelsetyp!", "Event sent!": "Händelse skickad!", @@ -604,8 +604,8 @@ "To continue using the %(homeserverDomain)s homeserver you must review and agree to our terms and conditions.": "För att fortsätta använda hemservern %(homeserverDomain)s måste du granska och godkänna våra villkor.", "Review terms and conditions": "Granska villkoren", "Old cryptography data detected": "Gammal krypteringsdata upptäckt", - "Unable to capture screen": "Det gick inte att ta skärmdump", - "Failed to add the following rooms to %(groupId)s:": "Det gick inte att lägga till följande rum till %(groupId)s:", + "Unable to capture screen": "Kunde inte ta skärmdump", + "Failed to add the following rooms to %(groupId)s:": "Misslyckades att lägga till följande rum till %(groupId)s:", "Missing roomId.": "Rums-ID saknas.", "This room is not recognised.": "Detta rum känns inte igen.", "Usage": "Användande", @@ -655,10 +655,10 @@ "%(severalUsers)schanged their name %(count)s times|one": "%(severalUsers)sbytte namn", "%(oneUser)schanged their name %(count)s times|other": "%(oneUser)sbytte namn %(count)s gånger", "%(oneUser)schanged their name %(count)s times|one": "%(oneUser)sbytte namn", - "%(severalUsers)schanged their avatar %(count)s times|other": "%(severalUsers)sändrade sin avatar %(count)s gånger", - "%(severalUsers)schanged their avatar %(count)s times|one": "%(severalUsers)sändrade sin avatar", - "%(oneUser)schanged their avatar %(count)s times|other": "%(oneUser)sändrade sin avatar %(count)s gånger", - "%(oneUser)schanged their avatar %(count)s times|one": "%(oneUser)sändrade sin avatar", + "%(severalUsers)schanged their avatar %(count)s times|other": "%(severalUsers)sbytte sin avatar %(count)s gånger", + "%(severalUsers)schanged their avatar %(count)s times|one": "%(severalUsers)sbytte sin avatar", + "%(oneUser)schanged their avatar %(count)s times|other": "%(oneUser)sbytte sin avatar %(count)s gånger", + "%(oneUser)schanged their avatar %(count)s times|one": "%(oneUser)sbytte sin avatar", "%(items)s and %(count)s others|other": "%(items)s och %(count)s till", "%(items)s and %(count)s others|one": "%(items)s och en till", "collapse": "fäll ihop", @@ -671,20 +671,20 @@ "Show these rooms to non-members on the community page and room list?": "Visa dessa rum för icke-medlemmar på gemenskapssidan och -rumslistan?", "Add rooms to the community": "Lägg till rum i gemenskapen", "Add to community": "Lägg till i gemenskap", - "Failed to invite users to community": "Det gick inte att bjuda in användare till gemenskapen", + "Failed to invite users to community": "Misslyckades att bjuda in användare till gemenskapen", "Mirror local video feed": "Spegla den lokala videoströmmen", "Community Invites": "Community-inbjudningar", "Invalid community ID": "Ogiltigt gemenskaps-ID", "'%(groupId)s' is not a valid community ID": "%(groupId)s är inte ett giltigt gemenskaps-ID", "New community ID (e.g. +foo:%(localDomain)s)": "Nytt gemenskaps-ID (t.ex. +foo:%(localDomain)s)", "Remove from community": "Ta bort från gemenskapen", - "Disinvite this user from community?": "Ta bort användarens inbjudan till gemenskapen?", + "Disinvite this user from community?": "Häv användarens inbjudan till gemenskapen?", "Remove this user from community?": "Ta bort användaren från gemenskapen?", - "Failed to remove user from community": "Det gick inte att ta bort användaren från gemenskapen", + "Failed to remove user from community": "Misslyckades att ta bort användaren från gemenskapen", "Filter community members": "Filtrera gemenskapsmedlemmar", - "Removing a room from the community will also remove it from the community page.": "Om du tar bort ett rum från gemenskapen tas det även bort från gemenskapens sida.", - "Failed to remove room from community": "Det gick inte att ta bort rummet från gemenskapen", - "Only visible to community members": "Endast synlig för gemenskapsmedlemmar", + "Removing a room from the community will also remove it from the community page.": "Om du tar bort ett rum från gemenskapen tas det även bort från gemenskapssidan.", + "Failed to remove room from community": "Misslyckades att ta bort rummet från gemenskapen", + "Only visible to community members": "Endast synligt för gemenskapsmedlemmar", "Filter community rooms": "Filtrera gemenskapsrum", "Community IDs cannot be empty.": "Gemenskaps-ID kan inte vara tomt.", "Community IDs may only contain characters a-z, 0-9, or '=_-./'": "Gemenskaps-ID får endast innehålla tecknen a-z, 0-9 och '=_-./'", @@ -703,7 +703,7 @@ "Community Settings": "Gemenskapsinställningar", "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.": "Det kan dröja upp till 30 minuter innan ändringar på gemenskapens namn och avatar blir synliga för andra användare.", "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.": "Dessa rum visas för gemenskapsmedlemmar på gemenskapssidan. Gemenskapsmedlemmar kan gå med i rummen genom att klicka på dem.", - "Add rooms to this community": "Lägg till rum i denna gemenskap", + "Add rooms to this community": "Lägg till rum till denna gemenskap", "%(inviter)s has invited you to join this community": "%(inviter)s har bjudit in dig till denna gemenskap", "Join this community": "Gå med i denna gemenskap", "Leave this community": "Lämna denna gemenskap", @@ -728,26 +728,26 @@ "Long Description (HTML)": "Lång beskrivning (HTML)", "Description": "Beskrivning", "Failed to load %(groupId)s": "Det gick inte att ladda %(groupId)s", - "Failed to withdraw invitation": "Det gick inte att ta bort inbjudan", - "Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Är du säker på att du vill ta bort %(roomName)s från %(groupId)s?", - "Failed to remove '%(roomName)s' from %(groupId)s": "Det gick inte att ta bort %(roomName)s från %(groupId)s", + "Failed to withdraw invitation": "Misslyckades att dra tillbaka inbjudan", + "Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Är du säker på att du vill ta bort '%(roomName)s' från %(groupId)s?", + "Failed to remove '%(roomName)s' from %(groupId)s": "Misslyckades att ta bort '%(roomName)s' från %(groupId)s", "Something went wrong!": "Något gick fel!", "The visibility of '%(roomName)s' in %(groupId)s could not be updated.": "Synligheten för '%(roomName)s' i %(groupId)s kunde inte uppdateras.", "Visibility in Room List": "Synlighet i rumslistan", - "Visible to everyone": "Synlig för alla", - "Please select the destination room for this message": "Välj vilket rum meddelandet ska skickas till", - "Disinvite this user?": "Ta bort användarens inbjudan?", - "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.": "Du kommer inte att kunna ångra den här ändringen eftersom du sänker din egen behörighetsnivå, om du är den sista privilegierade användaren i rummet blir det omöjligt att ändra behörigheter.", + "Visible to everyone": "Synligt för alla", + "Please select the destination room for this message": "Välj målrum för detta meddelande", + "Disinvite this user?": "Häv användarens inbjudan?", + "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.": "Du kommer inte att kunna ångra den här ändringen eftersom du degraderar dig själv. Om du är den sista privilegierade användaren i rummet blir det omöjligt att återfå behörigheter.", "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "Du kommer inte att kunna ångra den här ändringen eftersom du höjer användaren till samma behörighetsnivå som dig själv.", "unknown caller": "okänd uppringare", "To use it, just wait for autocomplete results to load and tab through them.": "För att använda detta, vänta på att autokompletteringen laddas och tabba igenom resultatet.", "Enable inline URL previews by default": "Aktivera inbäddad URL-förhandsvisning som standard", "Enable URL previews for this room (only affects you)": "Aktivera URL-förhandsvisning för detta rum (påverkar bara dig)", "Enable URL previews by default for participants in this room": "Aktivera URL-förhandsvisning som standard för deltagare i detta rum", - "You have enabled URL previews by default.": "Du har aktiverat URL-förhandsvisning som standard.", - "You have disabled URL previews by default.": "Du har inaktiverat URL-förhandsvisning som standard.", - "URL previews are enabled by default for participants in this room.": "URL-förhandsvisning är aktiverat som standard för deltagare i detta rum.", - "URL previews are disabled by default for participants in this room.": "URL-förhandsvisning är inaktiverat som standard för deltagare i detta rum.", + "You have enabled URL previews by default.": "Du har aktiverat URL-förhandsvisning som förval.", + "You have disabled URL previews by default.": "Du har inaktiverat URL-förhandsvisning som förval.", + "URL previews are enabled by default for participants in this room.": "URL-förhandsvisning är aktiverat som förval för deltagare i detta rum.", + "URL previews are disabled by default for participants in this room.": "URL-förhandsvisning är inaktiverat som förval för deltagare i detta rum.", "URL Previews": "URL-förhandsvisning", "Which rooms would you like to add to this summary?": "Vilka rum vill du lägga till i översikten?", "Add to summary": "Lägg till i översikt", @@ -766,16 +766,16 @@ "Key request sent.": "Nyckelbegäran skickad.", "Unban": "Avbanna", "Unban this user?": "Avbanna användaren?", - "Unmute": "Ta bort dämpning", + "Unmute": "Avtysta", "You don't currently have any stickerpacks enabled": "Du har för närvarande inga dekalpaket aktiverade", "Stickerpack": "Dekalpaket", "Hide Stickers": "Dölj dekaler", "Show Stickers": "Visa dekaler", - "Error decrypting audio": "Det gick inte att dekryptera ljud", - "Error decrypting image": "Det gick inte att dekryptera bild", - "Error decrypting video": "Det gick inte att dekryptera video", + "Error decrypting audio": "Fel vid avkryptering av ljud", + "Error decrypting image": "Fel vid avkryptering av bild", + "Error decrypting video": "Fel vid avkryptering av video", "Add an Integration": "Lägg till integration", - "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "Du skickas till en tredjepartswebbplats så att du kan autentisera ditt konto för användning med %(integrationsUrl)s. Vill du fortsätta?", + "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "Du kommer att skickas till en tredjepartswebbplats så att du kan autentisera ditt konto för användning med %(integrationsUrl)s. Vill du fortsätta?", "If you don't specify an email address, you won't be able to reset your password. Are you sure?": "Om du inte anger en epostadress, kan du inte återställa ditt lösenord. Är du säker?", "Popout widget": "Poppa ut widget", "were unbanned %(count)s times|other": "blev avbannade %(count)s gånger", @@ -827,15 +827,15 @@ "Permission Required": "Behörighet krävs", "You do not have permission to start a conference call in this room": "Du har inte behörighet att starta ett gruppsamtal i detta rum", "This event could not be displayed": "Den här händelsen kunde inte visas", - "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.": "I krypterade rum, som detta, är URL-förhandsvisning inaktiverad som standard för att säkerställa att din hemserver (där förhandsvisningar genereras) inte kan samla information om länkar du ser i rummet.", + "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.": "I krypterade rum, som detta, är URL-förhandsvisning inaktiverad som förval för att säkerställa att din hemserver (där förhandsvisningar genereras) inte kan samla information om länkar du ser i rummet.", "The email field must not be blank.": "Epost-fältet får inte vara tomt.", "The phone number field must not be blank.": "Telefonnummer-fältet får inte vara tomt.", "The password field must not be blank.": "Lösenords-fältet får inte vara tomt.", - "Failed to remove widget": "Det gick inte att ta bort widget", - "An error ocurred whilst trying to remove the widget from the room": "Ett fel uppstod vid borttagning av widget från rummet", - "Demote yourself?": "Sänk egen behörighetsnivå?", + "Failed to remove widget": "Misslyckades att radera widget", + "An error ocurred whilst trying to remove the widget from the room": "Ett fel inträffade vid borttagning av widget från rummet", + "Demote yourself?": "Degradera dig själv?", "Demote": "Degradera", - "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.": "När någon postar en URL i sitt meddelande, kan URL-förhandsvisning ge mer information om länken, såsom titel, beskrivning, och en bild från webbplatsen.", + "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.": "När någon lägger en URL i sitt meddelande, kan URL-förhandsvisning ge mer information om länken, såsom titel, beskrivning, och en bild från webbplatsen.", "You can't send any messages until you review and agree to our terms and conditions.": "Du kan inte skicka några meddelanden innan du granskar och godkänner våra villkor.", "System Alerts": "Systemvarningar", "Sorry, your homeserver is too old to participate in this room.": "Tyvärr, din hemserver är för gammal för att delta i det här rummet.", @@ -861,14 +861,14 @@ "Stop users from speaking in the old version of the room, and post a message advising users to move to the new room": "Hindra användare från att prata i den gamla rumsversionen och posta ett meddelande som rekommenderar användare att flytta till det nya rummet", "Put a link back to the old room at the start of the new room so people can see old messages": "Sätta en länk tillbaka till det gamla rummet i början av det nya rummet så att folk kan se gamla meddelanden", "Forces the current outbound group session in an encrypted room to be discarded": "Tvingar den aktuella utgående gruppsessionen i ett krypterat rum att överges", - "Unable to connect to Homeserver. Retrying...": "Det gick inte att ansluta till hemservern. Försöker igen…", + "Unable to connect to Homeserver. Retrying...": "Kunde inte ansluta till hemservern. Försöker igen…", "%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s satte huvudadressen för detta rum till %(address)s.", "%(senderName)s removed the main address for this room.": "%(senderName)s tog bort huvudadressen för detta rum.", "Add some now": "Lägg till några nu", "Please review and accept the policies of this homeserver:": "Granska och acceptera policyn för denna hemserver:", - "Before submitting logs, you must create a GitHub issue to describe your problem.": "Innan du skickar in loggar måste du skapa en GitHub-bugg för att beskriva problemet.", + "Before submitting logs, you must create a GitHub issue to describe your problem.": "Innan du skickar in loggar måste du skapa ett GitHub-ärende för att beskriva problemet.", "Updating %(brand)s": "Uppdaterar %(brand)s", - "Open Devtools": "Öppna Devtools", + "Open Devtools": "Öppna utvecklingsverktyg", "Show developer tools": "Visa utvecklarverktyg", "You are an administrator of this community. You will not be able to rejoin without an invite from another administrator.": "Du är administratör för denna gemenskap. Du kommer inte kunna gå med igen utan en inbjudan från en annan administratör.", "The file '%(fileName)s' exceeds this homeserver's size limit for uploads": "Filen '%(fileName)s' överstiger denna hemserverns storleksgräns för uppladdningar", @@ -1002,10 +1002,10 @@ "Headphones": "Hörlurar", "Folder": "Mapp", "Pin": "Häftstift", - "We've sent you an email to verify your address. Please follow the instructions there and then click the button below.": "Vi har skickat ett mail till dig för att verifiera din adress. Följ instruktionerna där och klicka sedan på knappen nedan.", - "Email Address": "Epostadress", + "We've sent you an email to verify your address. Please follow the instructions there and then click the button below.": "Vi har skickat ett e-brev till dig för att verifiera din adress. Följ instruktionerna där och klicka sedan på knappen nedan.", + "Email Address": "E-postadress", "Add an email address to configure email notifications": "Lägg till en e-postadress för att konfigurera e-postaviseringar", - "Unable to verify phone number.": "Det gick inte att verifiera telefonnumret.", + "Unable to verify phone number.": "Kunde inte verifiera telefonnumret.", "Verification code": "Verifieringskod", "Phone Number": "Telefonnummer", "Profile picture": "Profilbild", @@ -1022,7 +1022,7 @@ "For help with using %(brand)s, click here.": "För hjälp med att använda %(brand)s, klicka här.", "For help with using %(brand)s, click here or start a chat with our bot using the button below.": "För hjälp med att använda %(brand)s, klicka här eller starta en chatt med vår bott med knappen nedan.", "Chat with %(brand)s Bot": "Chatta med %(brand)s-bott", - "Help & About": "Hjälp och om", + "Help & About": "Hjälp & om", "Bug reporting": "Buggrapportering", "FAQ": "FAQ", "Versions": "Versioner", @@ -1030,14 +1030,14 @@ "Timeline": "Tidslinje", "Room list": "Rumslista", "Autocomplete delay (ms)": "Autokompletteringsfördröjning (ms)", - "Voice & Video": "Röst & Video", + "Voice & Video": "Röst & video", "Room information": "Rumsinformation", "Internal room ID:": "Internt rums-ID:", "Room version": "Rumsversion", "Room version:": "Rumsversion:", "Developer options": "Utvecklaralternativ", "Room Addresses": "Rumsadresser", - "That doesn't look like a valid email address": "Det verkar inte vara en giltig epostadress", + "That doesn't look like a valid email address": "Det verkar inte vara en giltig e-postadress", "Next": "Nästa", "Clear status": "Rensa status", "Update status": "Uppdatera status", @@ -1098,18 +1098,18 @@ "Modify widgets": "Ändra widgets", "Default role": "Standardroll", "Send messages": "Skicka meddelanden", - "Invite users": "Bjud in användare", + "Invite users": "Bjuda in användare", "Change settings": "Ändra inställningar", "Kick users": "Kicka användare", "Ban users": "Banna användare", "Remove messages": "Ta bort meddelanden", "Notify everyone": "Meddela alla", "Send %(eventType)s events": "Skicka %(eventType)s-händelser", - "Roles & Permissions": "Roller och behörigheter", + "Roles & Permissions": "Roller & behörigheter", "Enable encryption?": "Aktivera kryptering?", - "Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. Learn more about encryption.": "När det är aktiverat kan kryptering för ett rum inte inaktiveras. Meddelanden som skickas i ett krypterat rum kan inte ses av servern, utan endast av deltagarna i rummet. Att aktivera kryptering kan förhindra att många botar och bryggor att fungera korrekt. Läs mer om kryptering.", + "Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. Learn more about encryption.": "När det är aktiverat kan kryptering för ett rum inte inaktiveras. Meddelanden som skickas i ett krypterat rum kan inte ses av servern, utan endast av deltagarna i rummet. Att aktivera kryptering kan förhindra att många bottar och bryggor fungerar korrekt. Läs mer om kryptering.", "Encryption": "Kryptering", - "Once enabled, encryption cannot be disabled.": "Efter aktivering kan kryptering inte inaktiveras igen.", + "Once enabled, encryption cannot be disabled.": "Efter aktivering kan kryptering inte inaktiveras.", "Encrypted": "Krypterat", "Not now": "Inte nu", "Don't ask me again": "Fråga mig inte igen", @@ -1137,21 +1137,21 @@ "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.": "Är du säker? Du kommer att förlora dina krypterade meddelanden om dina nycklar inte säkerhetskopieras ordentligt.", "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Krypterade meddelanden är säkrade med totalsträckskryptering. Bara du och mottagaren/na har nycklarna för att läsa dessa meddelanden.", "Ignored users": "Ignorerade användare", - "Bulk options": "Bulkalternativ", + "Bulk options": "Massalternativ", "Accept all %(invitedRooms)s invites": "Acceptera alla %(invitedRooms)s inbjudningar", - "Security & Privacy": "Säkerhet och sekretess", + "Security & Privacy": "Säkerhet & sekretess", "Upgrade this room to the recommended room version": "Uppgradera detta rum till rekommenderad rumsversion", "Select the roles required to change various parts of the room": "Välj de roller som krävs för att ändra olika delar av rummet", "Changes to who can read history will only apply to future messages in this room. The visibility of existing history will be unchanged.": "Ändringar av vem som kan läsa historiken gäller endast för framtida meddelanden i detta rum. Synligheten för befintlig historik kommer att vara oförändrad.", "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Meddelanden i detta rum är säkrade med totalsträckskryptering. Bara du och mottagaren/na har nycklarna för att läsa dessa meddelanden.", - "Failed to revoke invite": "Det gick inte att återkalla inbjudan", - "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.": "Kunde inte återkalla inbjudan. Servern kan ha ett tillfälligt problem eller så har du inte tillräckligt med behörigheter för att återkalla inbjudan.", + "Failed to revoke invite": "Misslyckades att återkalla inbjudan", + "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.": "Kunde inte återkalla inbjudan. Servern kan ha ett tillfälligt problem eller så har du inte tillräckliga behörigheter för att återkalla inbjudan.", "Revoke invite": "Återkalla inbjudan", "Invited by %(sender)s": "Inbjuden av %(sender)s", - "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.": "Det uppstod ett fel vid uppdatering av rummets huvudadress. Det kanske inte tillåts av servern eller så inträffade ett tillfälligt fel.", + "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.": "Ett fel inträffade vid uppdatering av rummets huvudadress. Det kanske inte tillåts av servern, eller så inträffade ett tillfälligt fel.", "Main address": "Huvudadress", "Error updating flair": "Fel vid uppdatering av emblem", - "There was an error updating the flair for this room. The server may not allow it or a temporary error occurred.": "Det uppstod ett fel vid uppdatering av emblem för detta rum. Servern kanske inte tillåter det eller ett så inträffade tillfälligt fel.", + "There was an error updating the flair for this room. The server may not allow it or a temporary error occurred.": "Ett fel inträffade vid uppdatering av emblem för detta rum. Servern kanske inte tillåter det, eller ett så inträffade tillfälligt fel.", "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "Verifiera denna användare för att markera den som betrodd. Att kunna lita på användare ger en extra sinnesfrid när man använder totalsträckskrypterade meddelanden.", "A widget would like to verify your identity": "En widget vill verifiera din identitet", "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.": "En widget på %(widgetUrl)s vill verifiera din identitet. Genom att tillåta detta kommer widgeten att kunna verifiera ditt användar-ID, men inte agera som dig.", @@ -1167,16 +1167,16 @@ "Composer": "Meddelandefält", "Key backup": "Nyckelsäkerhetskopiering", "Never lose encrypted messages": "Förlora aldrig krypterade meddelanden", - "Securely back up your keys to avoid losing them. Learn more.": "Säkerhetskopiera dina nycklar på ett säkert sätt för att undvika att förlora dem. Läs mer.", - "Failed to load group members": "Det gick inte att ladda gruppmedlemmar", + "Securely back up your keys to avoid losing them. Learn more.": "Säkerhetskopiera dina nycklar på ett säkert sätt för att undvika att förlora dem. Lär dig mer.", + "Failed to load group members": "Misslyckades att ladda gruppmedlemmar", "Maximize apps": "Maximera appar", "Join": "Gå med", "Rotate counter-clockwise": "Rotera moturs", "Rotate clockwise": "Rotera medurs", "Power level": "Behörighetsnivå", - "Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?": "Det gick inte att hitta profiler för de Matrix-IDn som anges nedan - vill du bjuda in dem ändå?", + "Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?": "Kunde inte hitta profiler för de Matrix-ID:n som listas nedan - vill du bjuda in dem ändå?", "GitHub issue": "GitHub-ärende", - "Notes": "Noteringar", + "Notes": "Anteckningar", "You've previously used %(brand)s 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, %(brand)s needs to resync your account.": "Du har tidigare använt %(brand)s på %(host)s med lazy loading av medlemmar aktiverat. I den här versionen är lazy loading inaktiverat. Eftersom den lokala cachen inte är kompatibel mellan dessa två inställningar behöver %(brand)s synkronisera om ditt konto.", "If the other version of %(brand)s is still open in another tab, please close it as using %(brand)s on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "Om den andra versionen av %(brand)s fortfarande är öppen i en annan flik, stäng den eftersom användning av %(brand)s på samma värd med både lazy loading aktiverad och inaktiverad samtidigt kommer att orsaka problem.", "Incompatible local cache": "Inkompatibel lokal cache", @@ -1244,7 +1244,7 @@ "Public Name": "Offentligt namn", "Identity Server URL must be HTTPS": "URL för identitetsserver måste vara HTTPS", "Not a valid Identity Server (status code %(code)s)": "Inte en giltig identitetsserver (statuskod %(code)s)", - "Could not connect to Identity Server": "Det gick inte att ansluta till identitetsservern", + "Could not connect to Identity Server": "Kunde inte ansluta till identitetsservern", "Checking server": "Kontrollerar servern", "Change identity server": "Byt identitetsserver", "Disconnect from the identity server and connect to instead?": "Koppla ifrån från identitetsservern och anslut till istället?", @@ -1272,23 +1272,23 @@ "View older messages in %(roomName)s.": "Visa äldre meddelanden i %(roomName)s.", "Uploaded sound": "Uppladdat ljud", "Sounds": "Ljud", - "Notification sound": "Notifikationsljud", + "Notification sound": "Aviseringsljud", "Reset": "Återställ", "Set a new custom sound": "Ställ in ett nytt anpassat ljud", "Upgrade the room": "Uppgradera rummet", "Enable room encryption": "Aktivera rumskryptering", "Revoke": "Återkalla", "Share": "Dela", - "Discovery options will appear once you have added an email above.": "Upptäcktsalternativ visas när du har lagt till en e-postadress ovan.", + "Discovery options will appear once you have added an email above.": "Upptäcktsalternativ kommer att visas när du har lagt till en e-postadress ovan.", "Remove %(email)s?": "Ta bort %(email)s?", "Remove %(phone)s?": "Ta bort %(phone)s?", - "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.": "Ett textmeddelande har skickats till +%(msisdn)s. Ange verifieringskoden som det innehåller.", + "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.": "Ett SMS har skickats till +%(msisdn)s. Ange verifieringskoden som det innehåller.", "Edit message": "Redigera meddelande", - "No recent messages by %(user)s found": "Inga nyliga meddelanden av %(user)s hittades", - "Try scrolling up in the timeline to see if there are any earlier ones.": "Pröva att scrolla upp i tidslinjen för att se om det finns några tidigare.", - "Remove recent messages by %(user)s": "Ta bort nyliga meddelanden av %(user)s", - "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|other": "Du håller på att ta bort %(count)s meddelanden av %(user)s. Detta kan inte ångras. Vill du fortsätta?", - "For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.": "För en stor mängd meddelanden kan det ta lite tid. Vänligen uppdatera inte din klient under tiden.", + "No recent messages by %(user)s found": "Inga nyliga meddelanden från %(user)s hittades", + "Try scrolling up in the timeline to see if there are any earlier ones.": "Pröva att skrolla upp i tidslinjen för att se om det finns några tidigare.", + "Remove recent messages by %(user)s": "Ta bort nyliga meddelanden från %(user)s", + "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|other": "Du håller på att ta bort %(count)s meddelanden från %(user)s. Detta kan inte ångras. Vill du fortsätta?", + "For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.": "För en stor mängd meddelanden kan det ta lite tid. Vänligen ladda inte om din klient under tiden.", "Remove %(count)s messages|other": "Ta bort %(count)s meddelanden", "Deactivate user?": "Inaktivera användare?", "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?": "Vid inaktivering av användare loggas den ut och förhindras från att logga in igen. Den kommer dessutom att lämna alla rum den befinner sig i. Den här åtgärden kan inte ångras. Är du säker på att du vill inaktivera den här användaren?", @@ -1298,7 +1298,7 @@ "Italics": "Kursiv", "Strikethrough": "Genomstruken", "Code block": "Kodblock", - "Joining room …": "Gå med i rum …", + "Joining room …": "Går med i rummet …", "Loading …": "Laddar …", "Rejecting invite …": "Avvisar inbjudan …", "Join the conversation with an account": "Gå med i konversationen med ett konto", @@ -1307,8 +1307,8 @@ "Prompt before sending invites to potentially invalid matrix IDs": "Fråga innan inbjudningar skickas till potentiellt ogiltiga Matrix-ID:n", "Show all": "Visa alla", "reacted with %(shortName)s": "reagerade med %(shortName)s", - "Edited at %(date)s. Click to view edits.": "Ändrad %(date)s. Klicka för att visa ändringar.", - "edited": "ändrad", + "Edited at %(date)s. Click to view edits.": "Redigerat vid %(date)s. Klicka för att visa redigeringar.", + "edited": "redigerat", "Sign in to your Matrix account on ": "Logga in med ditt Matrix-konto på ", "Please install Chrome, Firefox, or Safari for the best experience.": "Installera Chrome, Firefox, eller Safari för den bästa upplevelsen.", "Couldn't load page": "Det gick inte att ladda sidan", @@ -1373,13 +1373,13 @@ "You're previewing %(roomName)s. Want to join it?": "Du förhandsgranskar %(roomName)s. Vill du gå med i det?", "%(roomName)s can't be previewed. Do you want to join it?": "%(roomName)s kan inte förhandsvisas. Vill du gå med i det?", "This room doesn't exist. Are you sure you're at the right place?": "Detta rum finns inte. Är du säker på att du är på rätt plats?", - "Try again later, or ask a room admin to check if you have access.": "Försök igen senare, eller be en rumsadministratör om att kontrollera om du har åtkomst.", - "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please submit a bug report.": "%(errcode)s returnerades när du försökte komma åt rummet. Om du tror att du ser det här meddelandet felaktigt, vänligen skicka in en felrapport.", - "Failed to connect to integration manager": "Det gick inte att ansluta till integrationshanterare", + "Try again later, or ask a room admin to check if you have access.": "Försök igen senare, eller be en rumsadministratör att kolla om du har åtkomst.", + "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please submit a bug report.": "%(errcode)s returnerades när du försökte komma åt rummet. Om du tror att du ser det här meddelandet felaktigt, vänligen skicka in en buggrapport.", + "Failed to connect to integration manager": "Kunde inte ansluta till integrationshanterare", "React": "Reagera", "Message Actions": "Meddelandeåtgärder", "Show image": "Visa bild", - "You have ignored this user, so their message is hidden. Show anyways.": "Du har ignorerat denna användare, så deras meddelande är dolt. Visa ändå.", + "You have ignored this user, so their message is hidden. Show anyways.": "Du har ignorerat den här användaren, så dess meddelande är dolt. Visa ändå.", "You verified %(name)s": "Du verifierade %(name)s", "You cancelled verifying %(name)s": "Du avbröt verifiering av %(name)s", "%(name)s cancelled verifying": "%(name)s avbröt verifiering", @@ -1391,18 +1391,18 @@ "You sent a verification request": "Du skickade en verifieringsbegäran", "Reactions": "Reaktioner", " reacted with %(content)s": " reagerade med %(content)s", - "Frequently Used": "Ofta använd", - "Smileys & People": "Smileys & Människor", - "Animals & Nature": "Djur & Natur", - "Food & Drink": "Mat & Dryck", + "Frequently Used": "Ofta använda", + "Smileys & People": "Smileys & personer", + "Animals & Nature": "Djur & natur", + "Food & Drink": "Mat & dryck", "Activities": "Aktiviteter", - "Travel & Places": "Resor & Platser", + "Travel & Places": "Resor & platser", "Objects": "Objekt", "Symbols": "Symboler", "Flags": "Flaggor", "Quick Reactions": "Snabbreaktioner", "Cancel search": "Avbryt sökningen", - "Any of the following data may be shared:": "Någon av följande data kan delas:", + "Any of the following data may be shared:": "Vissa av följande data kan delas:", "Your display name": "Ditt visningsnamn", "Your avatar URL": "Din avatar-URL", "Your user ID": "Ditt användar-ID", @@ -1410,13 +1410,13 @@ "%(brand)s URL": "%(brand)s-URL", "Room ID": "Rums-ID", "Widget ID": "Widget-ID", - "Using this widget may share data with %(widgetDomain)s & your Integration Manager.": "Att använda denna widget kan dela data med %(widgetDomain)s och din Integrationshanterare.", + "Using this widget may share data with %(widgetDomain)s & your Integration Manager.": "Att använda denna widget kan dela data med %(widgetDomain)s och din integrationshanterare.", "Using this widget may share data with %(widgetDomain)s.": "Att använda denna widget kan dela data med %(widgetDomain)s.", "Widgets do not use message encryption.": "Widgets använder inte meddelandekryptering.", "Widget added by": "Widget tillagd av", - "This widget may use cookies.": "Denna widget kan använda cookies.", + "This widget may use cookies.": "Denna widget kan använda kakor.", "More options": "Fler alternativ", - "Please create a new issue on GitHub so that we can investigate this bug.": "Vänligen skapa ett nytt ärende på GitHub så att vi kan undersöka detta fel.", + "Please create a new issue on GitHub so that we can investigate this bug.": "Vänligen skapa ett nytt ärende på GitHub så att vi kan undersöka denna bugg.", "Rotate Left": "Rotera vänster", "Rotate Right": "Rotera höger", "Language Dropdown": "Språkmeny", @@ -1424,12 +1424,12 @@ "%(severalUsers)smade no changes %(count)s times|one": "%(severalUsers)sgjorde inga ändringar", "%(oneUser)smade no changes %(count)s times|other": "%(oneUser)sgjorde inga ändringar %(count)s gånger", "%(oneUser)smade no changes %(count)s times|one": "%(oneUser)sgjorde inga ändringar", - "e.g. my-room": "t.ex. mitt rum", + "e.g. my-room": "t.ex. mitt-rum", "Some characters not allowed": "Vissa tecken är inte tillåtna", - "Use an identity server to invite by email. Use the default (%(defaultIdentityServerName)s) or manage in Settings.": "Använd en identitetsserver för att bjuda in via epost. Använd standard (%(defaultIdentityServerName)s) eller hantera i Inställningar.", - "Use an identity server to invite by email. Manage in Settings.": "Använd en identitetsserver för att bjuda in via epost. Hantera i Inställningar.", + "Use an identity server to invite by email. Use the default (%(defaultIdentityServerName)s) or manage in Settings.": "Använd en identitetsserver för att bjuda in via e-post. Använd förval (%(defaultIdentityServerName)s) eller hantera i inställningarna.", + "Use an identity server to invite by email. Manage in Settings.": "Använd en identitetsserver för att bjuda in via e-post. Hantera i inställningarna.", "Close dialog": "Stäng dialogrutan", - "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.": "Berätta vad som gick fel eller, bättre, skapa ett GitHub-ärende som beskriver problemet.", + "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.": "Berätta vad som gick fel, eller skapa ännu hellre ett GitHub-ärende som beskriver problemet.", "View Servers in Room": "Visa servrar i rum", "Recent Conversations": "Senaste konversationerna", "Suggestions": "Förslag", @@ -1470,22 +1470,22 @@ "Unknown (user, session) pair:": "Okänt (användare, session)-par:", "Session already verified!": "Sessionen är redan verifierad!", "WARNING: Session already verified, but keys do NOT MATCH!": "VARNING: Sessionen har redan verifierats, men nycklarna MATCHAR INTE!", - "Unable to revoke sharing for email address": "Det gick inte att återkalla delning för e-postadress", - "Unable to share email address": "Det gick inte att dela e-postadress", + "Unable to revoke sharing for email address": "Kunde inte återkalla delning för e-postadress", + "Unable to share email address": "Kunde inte dela e-postadress", "Your email address hasn't been verified yet": "Din e-postadress har inte verifierats än", "Click the link in the email you received to verify and then click continue again.": "Klicka på länken i e-postmeddelandet för att bekräfta och klicka sedan på Fortsätt igen.", "Verify the link in your inbox": "Verifiera länken i din inkorg", "Complete": "Färdigställ", - "Unable to revoke sharing for phone number": "Det gick inte att återkalla delning för telefonnummer", - "Unable to share phone number": "Det gick inte att dela telefonnummer", - "Please enter verification code sent via text.": "Ange verifieringskod skickad via textmeddelande.", - "Discovery options will appear once you have added a phone number above.": "Upptäcktsalternativ visas när du har lagt till ett telefonnummer ovan.", + "Unable to revoke sharing for phone number": "Kunde inte återkalla delning för telefonnummer", + "Unable to share phone number": "Kunde inte dela telefonnummer", + "Please enter verification code sent via text.": "Ange verifieringskod skickad via SMS.", + "Discovery options will appear once you have added a phone number above.": "Upptäcktsalternativ kommer att visas när du har lagt till ett telefonnummer ovan.", "Verify session": "Verifiera sessionen", "Session name": "Sessionsnamn", "Session key": "Sessionsnyckel", "Automatically invite users": "Bjud in användare automatiskt", "Upgrade private room": "Uppgradera privat rum", - "Upgrade public room": "Uppgradera publikt rum", + "Upgrade public room": "Uppgradera offentligt rum", "Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.": "Att uppgradera ett rum är en avancerad åtgärd och rekommenderas vanligtvis när ett rum är instabilt på grund av buggar, saknade funktioner eller säkerhetsproblem.", "This usually only affects how the room is processed on the server. If you're having problems with your %(brand)s, please report a bug.": "Detta påverkar vanligtvis bara hur rummet bearbetas på servern. Om du har problem med %(brand)s, rapportera ett fel.", "You'll upgrade this room from to .": "Du kommer att uppgradera detta rum från till .", @@ -1831,5 +1831,249 @@ "Read Marker lifetime (ms)": "Läsmarkörens livstid (ms)", "Read Marker off-screen lifetime (ms)": "Läsmarkörens livstid utanför skärmen (ms)", "Session ID:": "Sessions-ID:", - "Session key:": "Sessions nyckel:" + "Session key:": "Sessionsnyckel:", + "Message search": "Meddelandesök", + "Cross-signing": "Korssignering", + "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Din serveradministratör har inaktiverat totalsträckskryptering som förval för privata rum och direktmeddelanden.", + "Where you’re logged in": "Var du har loggat in", + "Manage the names of and sign out of your sessions below or verify them in your User Profile.": "Hantera namn för och logga ut ur dina sessioner nedan eller verifiera dem i din användarprofil.", + "A session's public name is visible to people you communicate with": "En sessions publika namn visas för personer du kommunicerar med", + "This room is bridging messages to the following platforms. Learn more.": "Det här rummet bryggar meddelanden till följande plattformar. Lär dig mer.", + "This room isn’t bridging messages to any platforms. Learn more.": "Det här rummet bryggar inte meddelanden till några plattformar. Lär dig mer.", + "Bridges": "Bryggor", + "Browse": "Bläddra", + "Error changing power level requirement": "Fel vid ändring av behörighetskrav", + "An error occurred changing the room's power level requirements. Ensure you have sufficient permissions and try again.": "Ett fel inträffade vid ändring av rummets krav på behörighetsnivå. Försäkra att du har tillräcklig behörighet och försök igen.", + "Error changing power level": "Fel vid ändring av behörighetsnivå", + "An error occurred changing the user's power level. Ensure you have sufficient permissions and try again.": "Ett fel inträffade vid ändring av användarens behörighetsnivå. Försäkra att du har tillräcklig behörighet och försök igen.", + "To link to this room, please add an address.": "För att länka till det här rummet, lägg till en adress.", + "This user has not verified all of their sessions.": "Den här användaren har inte verifierat alla sina sessioner.", + "You have not verified this user.": "Du har inte verifierat den här användaren.", + "You have verified this user. This user has verified all of their sessions.": "Du har verifierat den här användaren. Den här användaren har verifierat alla sina sessioner.", + "Someone is using an unknown session": "Någon använder en okänd session", + "This room is end-to-end encrypted": "Det här rummet är totalsträckskrypterat", + "Everyone in this room is verified": "Alla i det här rummet är verifierade", + "Mod": "Mod", + "Your key share request has been sent - please check your other sessions for key share requests.": "Din nyckeldelningsbegäran har skickats - vänligen kolla dina andra sessioner för nyckeldelningsbegäran.", + "Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "Nyckeldelningsbegäran skickas till dina andra sessioner automatiskt. Om du avvisade eller avfärdade nyckeldelningsbegäran på dina andra sessioner, klicka här för att begära nycklarna för den här sessionen igen.", + "If your other sessions do not have the key for this message you will not be able to decrypt them.": "Om dina andra sessioner inte har nyckeln för det här meddelandet så kommer du inte kunna avkryptera dem.", + "Re-request encryption keys from your other sessions.": "Återförfråga krypteringsnycklar från dina andra sessioner.", + "This message cannot be decrypted": "Det här meddelandet kan inte avkrypteras", + "Encrypted by an unverified session": "Krypterat av en overifierad session", + "Unencrypted": "Okrypterat", + "Encrypted by a deleted session": "Krypterat av en raderad session", + "The authenticity of this encrypted message can't be guaranteed on this device.": "Det krypterade meddelandets äkthet kan inte garanteras på den här enheten.", + "Scroll to most recent messages": "Skrolla till de senaste meddelandena", + "Emoji picker": "Emojiväljare", + "Send a reply…": "Skicka ett svar…", + "Send a message…": "Skicka ett meddelande…", + "No recently visited rooms": "Inga nyligen besökta rum", + "People": "Personer", + "Add room": "Lägg till rum", + "Explore community rooms": "Utforska gemenskapens rum", + "Explore public rooms": "Utforska offentliga rum", + "Custom Tag": "Anpassad etikett", + "Can't see what you’re looking for?": "Kan du inte se det du letar efter?", + "Explore all public rooms": "Utforska alla offentliga rum", + "%(count)s results|other": "%(count)s resultat", + "You were kicked from %(roomName)s by %(memberName)s": "Du blev kickad från %(roomName)s av %(memberName)s", + "Reason: %(reason)s": "Anledning: %(reason)s", + "Forget this room": "Glöm det här rummet", + "You were banned from %(roomName)s by %(memberName)s": "Du blev bannad från %(roomName)s av %(memberName)s", + "Something went wrong with your invite to %(roomName)s": "Någonting gick fel med din inbjudan till %(roomName)s", + "An error (%(errcode)s) was returned while trying to validate your invite. You could try to pass this information on to a room admin.": "Ett fel (%(errcode)s) returnerades vid försöket att validera din inbjudan. Du kan försöka att ge den här informationen till rumsadministratören.", + "You can only join it with a working invite.": "Du kan bara gå med i det med en fungerande inbjudan.", + "You can still join it because this is a public room.": "Du kan fortfarande gå med eftersom det här är ett offentligt rum.", + "This invite to %(roomName)s was sent to %(email)s which is not associated with your account": "Denna inbjudan till %(roomName)s skickades till %(email)s vilken inte är associerad med det här kontot", + "Link this email with your account in Settings to receive invites directly in %(brand)s.": "Länka den här e-postadressen med ditt konto in inställningarna för att motta inbjudningar direkt i %(brand)s.", + "This invite to %(roomName)s was sent to %(email)s": "Denna inbjudan till %(roomName)s skickades till %(email)s", + "Use an identity server in Settings to receive invites directly in %(brand)s.": "Använd en identitetsserver i inställningarna för att motta inbjudningar direkt i %(brand)s.", + "Share this email in Settings to receive invites directly in %(brand)s.": "Dela denna e-postadress i inställningarna för att motta inbjudningar direkt i %(brand)s.", + "Reject & Ignore user": "Avvisa och ignorera användare", + "Appearance": "Utseende", + "Show rooms with unread messages first": "Visa rum med olästa meddelanden först", + "Show previews of messages": "Visa förhandsvisningar av meddelanden", + "Sort by": "Sortera efter", + "Activity": "Aktivitet", + "A-Z": "A-Ö", + "List options": "Lista alternativ", + "Jump to first unread room.": "Hoppa till första olästa rum.", + "Jump to first invite.": "Hoppa till första inbjudan.", + "Show %(count)s more|other": "Visa %(count)s till", + "Show %(count)s more|one": "Visa %(count)s till", + "Use default": "Använd förval", + "Mentions & Keywords": "Benämningar & nyckelord", + "Notification options": "Aviseringsinställningar", + "Forget Room": "Glöm rum", + "Favourited": "Favoritmarkerade", + "Leave Room": "Lämna rum", + "Room options": "Rumsinställningar", + "%(count)s unread messages including mentions.|other": "%(count)s olästa meddelanden inklusive omnämnanden.", + "%(count)s unread messages including mentions.|one": "1 oläst omnämnande.", + "%(count)s unread messages.|other": "%(count)s olästa meddelanden.", + "%(count)s unread messages.|one": "1 oläst meddelande.", + "Unread messages.": "Olästa meddelanden.", + "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Att uppgradera det här rummet kommer att stänga den nuvarande instansen av rummet och skapa ett uppgraderat rum med samma namn.", + "This room has already been upgraded.": "Det här rummet har redan uppgraderats.", + "This room is running room version , which this homeserver has marked as unstable.": "Det här rummet kör rumsversion , vilket den här hemservern har markerat som instabil.", + "Unknown Command": "Okänt kommando", + "Unrecognised command: %(commandText)s": "Okänt kommando: %(commandText)s", + "You can use /help to list available commands. Did you mean to send this as a message?": "Du kan använda /help för att lista tillgängliga kommandon. Menade du att skicka detta som ett meddelande?", + "Hint: Begin your message with // to start it with a slash.": "Tips: Börja ditt meddelande med // för att starta det med ett snedstreck.", + "Send as message": "Skicka som meddelande", + "Mark all as read": "Markera alla som lästa", + "There was an error updating the room's alternative addresses. It may not be allowed by the server or a temporary failure occurred.": "Ett fel inträffade vid uppdatering av rummets alternativa adresser. Det kanske inte tillåts av servern, eller så inträffade ett tillfälligt fel.", + "Error creating address": "Fel vid skapande av adress", + "There was an error creating that address. It may not be allowed by the server or a temporary failure occurred.": "Ett fel inträffade vid skapande av adressen. Det kanske inte tillåts av servern, eller så inträffade ett tillfälligt fel.", + "You don't have permission to delete the address.": "Du har inte behörighet att radera den där adressen.", + "There was an error removing that address. It may no longer exist or a temporary error occurred.": "Ett fel inträffade vid borttagning av adressen. Den kanske inte längre existerar, eller så inträffade ett tillfälligt fel.", + "Error removing address": "Fel vi borttagning av adress", + "Local address": "Lokal adress", + "Published Addresses": "Publicerade adresser", + "Published addresses can be used by anyone on any server to join your room. To publish an address, it needs to be set as a local address first.": "Publicerade adresser kan användas av vem som helst på vilken server som helst för att gå med i ditt rum. För att publicera en adress måste den ställas in som en lokal adress först.", + "Other published addresses:": "Andra publicerade adresser:", + "No other published addresses yet, add one below": "Inga andra publicerade adresser än, lägg till en nedan", + "New published address (e.g. #alias:server)": "Ny publicerad adress (t.ex. #alias:server)", + "Local Addresses": "Lokala adresser", + "Set addresses for this room so users can find this room through your homeserver (%(localDomain)s)": "Ange adresser för det här rummet så att användare kan hitta det här rummet via din hemserver (%(localDomain)s)", + "Waiting for you to accept on your other session…": "Väntar på att du ska acceptera din andra session…", + "Waiting for %(displayName)s to accept…": "Väntar på att %(displayName)s ska acceptera…", + "Accepting…": "Accepterar…", + "Start Verification": "Starta verifiering", + "Messages in this room are end-to-end encrypted.": "Meddelanden i det här rummet är totalsträckskrypterade.", + "Your messages are secured and only you and the recipient have the unique keys to unlock them.": "Dina meddelanden är säkrade och endast du och mottagaren har de unika nycklarna för att låsa upp dem.", + "Messages in this room are not end-to-end encrypted.": "Meddelanden i detta rum är inte totalsträckskrypterade.", + "In encrypted rooms, your messages are secured and only you and the recipient have the unique keys to unlock them.": "I krypterade rum är dina meddelanden säkrade och endast du och mottagaren har de unika nycklarna för att låsa upp dem.", + "Verify User": "Verifiera användare", + "For extra security, verify this user by checking a one-time code on both of your devices.": "För extra säkerhet, verifiera den här användaren genom att kolla en engångskod på båda era enheter.", + "Your messages are not secure": "Dina meddelanden är inte säkra", + "One of the following may be compromised:": "Någon av följande kan vara äventyrad:", + "Your homeserver": "Din hemserver", + "The homeserver the user you’re verifying is connected to": "Hemservern som användaren du verifierar är ansluten till", + "Yours, or the other users’ internet connection": "Din eller den andra användarens internetanslutning", + "Yours, or the other users’ session": "Din eller den andra användarens session", + "Trusted": "Betrodd", + "Not trusted": "Inte betrodd", + "%(count)s verified sessions|other": "%(count)s verifierade sessioner", + "%(count)s verified sessions|one": "1 verifierad session", + "Hide verified sessions": "Dölj verifierade sessioner", + "%(count)s sessions|other": "%(count)s sessioner", + "%(count)s sessions|one": "%(count)s session", + "Hide sessions": "Dölj sessioner", + "Direct message": "Direktmeddelande", + "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|one": "Du håller på att ta bort 1 meddelande från %(user)s. Detta kan inte ångras. Vill du fortsätta?", + "Remove %(count)s messages|one": "Ta bort 1 meddelande", + "%(role)s in %(roomName)s": "%(role)s i %(roomName)s", + "Failed to deactivate user": "Misslyckades att inaktivera användaren", + "This client does not support end-to-end encryption.": "Den här klienten stöder inte totalsträckskryptering.", + "Security": "Säkerhet", + "The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what %(brand)s supports. Try with a different client.": "Sessionen du försöker verifiera stöder inte skanning av en QR-kod eller emoji-verifiering, vilket är vad %(brand)s stöder. Försök med en annan klient.", + "Verify by scanning": "Verifiera med skanning", + "Ask %(displayName)s to scan your code:": "Be %(displayName)s att skanna din kod:", + "If you can't scan the code above, verify by comparing unique emoji.": "Om du inte kan skanna koden ovan, verifiera genom att jämföra unika emojier.", + "Verify by comparing unique emoji.": "Verifiera genom att jämföra unika emojier.", + "Verify by emoji": "Verifiera med emoji", + "Almost there! Is your other session showing the same shield?": "Nästan klar! Visar din andra session samma sköld?", + "Almost there! Is %(displayName)s showing the same shield?": "Nästan klar! Visar %(displayName)s samma sköld?", + "Verify all users in a room to ensure it's secure.": "Verifiera alla användare i ett rum för att försäkra att det är säkert.", + "In encrypted rooms, verify all users to ensure it’s secure.": "I krypterade rum, verifiera alla användare för att försäkra att det är säkert.", + "You've successfully verified your device!": "Du har verifierat din enhet framgångsrikt!", + "You've successfully verified %(deviceName)s (%(deviceId)s)!": "Du har verifierat %(deviceName)s (%(deviceId)s) framgångsrikt!", + "You've successfully verified %(displayName)s!": "Du har verifierat %(displayName)s framgångsrikt!", + "Verified": "Verifierad", + "Got it": "Uppfattat", + "Start verification again from the notification.": "Starta verifiering igen från aviseringen.", + "Start verification again from their profile.": "Starta verifiering igen från deras profil.", + "Verification timed out.": "Verifieringen löpte ut.", + "You cancelled verification on your other session.": "Du avbröt verifieringen på din andra session.", + "%(displayName)s cancelled verification.": "%(displayName)s avbröt verifiering.", + "You cancelled verification.": "Du avbröt verifiering.", + "Verification cancelled": "Verifiering avbruten", + "Compare emoji": "Jämför emoji", + "Encryption enabled": "Kryptering aktiverad", + "Messages in this room are end-to-end encrypted. Learn more & verify this user in their user profile.": "Meddelanden i det här rummet är totalsträckskrypterade. Lär dig mer och verifiera den här användaren i deras användarprofil.", + "Encryption not enabled": "Kryptering är inte aktiverad", + "The encryption used by this room isn't supported.": "Krypteringen som används i det här rummet stöds inte.", + "You declined": "Du avslog", + "%(name)s declined": "%(name)s avslog", + "Accepting …": "Accepterar …", + "Declining …": "Avslår …", + "Message deleted": "Meddelande raderat", + "Message deleted by %(name)s": "Meddelande raderat av %(name)s", + "Message deleted on %(date)s": "Meddelande raderat vid %(date)s", + "Edited at %(date)s": "Redigerat vid %(date)s", + "Click to view edits": "Klicka för att visa redigeringar", + "Can't load this message": "Kan inte ladda det här meddelandet", + "Submit logs": "Skicka loggar", + "Categories": "Kategorier", + "Information": "Information", + "QR Code": "QR-kod", + "Room address": "Rumsadress", + "Please provide a room address": "Vänligen välj en rumsadress", + "This address is available to use": "Adressen är tillgänglig", + "This address is already in use": "Adressen är upptagen", + "Sign in with single sign-on": "Logga in med single sign-on", + "Enter a server name": "Ange ett servernamn", + "Looks good": "Ser bra ut", + "Can't find this server or its room list": "Kan inte hitta den här servern eller dess rumslista", + "All rooms": "Alla rum", + "Your server": "Din server", + "Are you sure you want to remove %(serverName)s": "Är du säker på att du vill ta bort %(serverName)s", + "Remove server": "Ta bort server", + "Matrix": "Matrix", + "Add a new server": "Lägg till en ny server", + "Enter the name of a new server you want to explore.": "Ange namnet för en ny server du vill utforska.", + "Server name": "Servernamn", + "Add a new server...": "Lägg till en ny server…", + "%(networkName)s rooms": "%(networkName)s-rum", + "Matrix rooms": "Matrix-rum", + "Preparing to download logs": "Förbereder nedladdning av loggar", + "Reminder: Your browser is unsupported, so your experience may be unpredictable.": "Påminnelse: Din webbläsare stöds inte, så din upplevelse kan vara oförutsägbar.", + "Download logs": "Ladda ner loggar", + "If there is additional context that would help in analysing the issue, such as what you were doing at the time, room IDs, user IDs, etc., please include those things here.": "Om det finns ytterligare sammanhang som kan hjälpa för att analysera problemet, till exempel vad du gjorde vid den tiden, rums-ID:n, användar-ID:n o.s.v., vänligen inkludera dessa saker här.", + "Unable to load commit detail: %(msg)s": "Kunde inte ladda commit-detalj: %(msg)s", + "Add another email": "Lägg till en till e-postadress", + "People you know on %(brand)s": "Personer du känner på %(brand)s", + "Show": "Visa", + "Send %(count)s invites|other": "Skicka %(count)s inbjudningar", + "Send %(count)s invites|one": "Skicka %(count)s inbjudan", + "Invite people to join %(communityName)s": "Bjud in folk att gå med i %(communityName)s", + "Removing…": "Tar bort…", + "Destroy cross-signing keys?": "Förstöra korssigneringsnycklar?", + "Deleting cross-signing keys is permanent. Anyone you have verified with will see security alerts. You almost certainly don't want to do this, unless you've lost every device you can cross-sign from.": "Radering av korssigneringsnycklar är permanent. Alla du har verifierat med kommer att se säkerhetsvarningar. Du vill troligen inte göra detta, såvida du inte har tappat bort alla enheter du kan korssignera från.", + "Clear cross-signing keys": "Rensa korssigneringsnycklar", + "Clear all data in this session?": "Rensa all data i den här sessionen?", + "Clearing all data from this session is permanent. Encrypted messages will be lost unless their keys have been backed up.": "Rensning av all data från den här sessionen är permanent. Krypterade meddelande kommer att förloras om inte deras nycklar har säkerhetskopierats.", + "Clear all data": "Rensa all data", + "There was an error creating your community. The name may be taken or the server is unable to process your request.": "Ett fel inträffade vid skapande av din gemenskap. Namnet kan vara upptaget eller så kan servern inte hantera din begäran.", + "Community ID: +:%(domain)s": "Gemenskaps-ID: +:%(domain)s", + "Use this when referencing your community to others. The community ID cannot be changed.": "Använd detta när du hänvisar andra till din gemenskap. Gemenskaps-ID:t kan inte ändras.", + "You can change this later if needed.": "Du kan ändra detta senare om det behövs.", + "What's the name of your community or team?": "Vad är namnet på din gemenskap eller ditt lag?", + "Enter name": "Ange namn", + "Add image (optional)": "Lägg till bild (valfritt)", + "An image will help people identify your community.": "En bild hjälper folk att identifiera din gemenskap.", + "Please enter a name for the room": "Vänligen ange ett namn för rummet", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "Privata rum kan endast hittas och gås med i med inbjudan. Offentliga rum kan hittas och gås med i av vem som helst.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Privata rum kan endast hittas och gås med i med inbjudan. Offentliga rum kan hittas och gås med i av vem som helst i den här gemenskapen.", + "You can’t disable this later. Bridges & most bots won’t work yet.": "Du kan inte inaktivera det här senare. Bryggor och bottar kommer inte fungera än.", + "Enable end-to-end encryption": "Aktivera totalsträckskryptering", + "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Du kanske vill aktivera detta om rummet endast kommer att användas för samarbete med interna lag på din hemserver. Detta kan inte ändras senare.", + "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Du kanske vill inaktivera detta om rummet kommer att användas för samarbete med externa lag som har sin egen hemserver. Detta kan inte ändras senare.", + "Create a public room": "Skapa ett offentligt rum", + "Create a private room": "Skapa ett privat rum", + "Create a room in %(communityName)s": "Skapa ett rum i %(communityName)s", + "Topic (optional)": "Ämne (valfritt)", + "Make this room public": "Gör det här rummet offentligt", + "Hide advanced": "Dölj avancerat", + "Show advanced": "Visa avancerat", + "Block anyone not part of %(serverName)s from ever joining this room.": "Blockera alla som inte är medlem i %(serverName)s från att någonsin gå med i det här rummet.", + "To avoid losing your chat history, you must export your room keys before logging out. You will need to go back to the newer version of %(brand)s to do this": "För att undvika att förlora din chatthistorik måste du exportera dina rumsnycklar innan du loggar ut. Du behöver gå tillbaka till den nyare versionen av %(brand)s för att göra detta", + "You've previously used a newer version of %(brand)s with this session. To use this version again with end to end encryption, you will need to sign out and back in again.": "Du har tidigare använt en nyare version av %(brand)s med den här sessionen. Om du vill använda den här versionen igen med totalsträckskryptering behöver du logga ut och logga in igen.", + "Incompatible Database": "Inkompatibel databas", + "Continue With Encryption Disabled": "Fortsätt med kryptering inaktiverad", + "Confirm your account deactivation by using Single Sign On to prove your identity.": "Bekräfta din kontoinaktivering genom att använda single sign-on för att bevisa din identitet.", + "Are you sure you want to deactivate your account? This is irreversible.": "Är du säker på att du vill inaktivera ditt konto? Detta är oåterkalleligt.", + "Confirm account deactivation": "Bekräfta kontoinaktivering", + "Security & privacy": "Säkerhet & sekretess" } From 2219a6db9b787a059ba55740d10a422eb25f1e06 Mon Sep 17 00:00:00 2001 From: purjolini Date: Mon, 31 Aug 2020 19:22:30 +0000 Subject: [PATCH 045/286] Translated using Weblate (Swedish) Currently translated at 86.1% (2030 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index fff7f90b82..b45a6e5a32 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -860,7 +860,7 @@ "Update any local room aliases to point to the new room": "Uppdatera lokala rumsalias att peka på det nya rummet", "Stop users from speaking in the old version of the room, and post a message advising users to move to the new room": "Hindra användare från att prata i den gamla rumsversionen och posta ett meddelande som rekommenderar användare att flytta till det nya rummet", "Put a link back to the old room at the start of the new room so people can see old messages": "Sätta en länk tillbaka till det gamla rummet i början av det nya rummet så att folk kan se gamla meddelanden", - "Forces the current outbound group session in an encrypted room to be discarded": "Tvingar den aktuella utgående gruppsessionen i ett krypterat rum att överges", + "Forces the current outbound group session in an encrypted room to be discarded": "Tvingar den aktuella externa gruppsessionen i ett krypterat rum att överges", "Unable to connect to Homeserver. Retrying...": "Kunde inte ansluta till hemservern. Försöker igen…", "%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s satte huvudadressen för detta rum till %(address)s.", "%(senderName)s removed the main address for this room.": "%(senderName)s tog bort huvudadressen för detta rum.", From 66ea469b2ccfc6939d55eb809425d854612d26a0 Mon Sep 17 00:00:00 2001 From: Yes Date: Mon, 31 Aug 2020 19:23:49 +0000 Subject: [PATCH 046/286] Translated using Weblate (Swedish) Currently translated at 86.1% (2030 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index b45a6e5a32..df82de8347 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -589,7 +589,7 @@ "Incorrect password": "Felaktigt lösenord", "State Key": "Lägesnyckel", "Send Account Data": "Skicka kontodata", - "Explore Account Data": "Utforska kontodata", + "Explore Account Data": "Utforska Konto Data", "Toolbox": "Verktygslåda", "Developer Tools": "Utvecklarverktyg", "Clear Storage and Sign Out": "Rensa lagring och logga ut", @@ -1436,7 +1436,7 @@ "Show more": "Visa mer", "Direct Messages": "Direktmeddelanden", "Go": "Gå", - "Waiting for partner to confirm...": "Väntar på att kompanjon ska bekräfta...", + "Waiting for partner to confirm...": "Väntar på att partnern ska bekräfta...", "Incoming Verification Request": "Inkommande verifieringsbegäran", "Integrations are disabled": "Integrationer är inaktiverade", "Enable 'Manage Integrations' in Settings to do this.": "Aktivera \"Hantera integrationer\" i Inställningar för att göra detta.", @@ -2075,5 +2075,9 @@ "Confirm your account deactivation by using Single Sign On to prove your identity.": "Bekräfta din kontoinaktivering genom att använda single sign-on för att bevisa din identitet.", "Are you sure you want to deactivate your account? This is irreversible.": "Är du säker på att du vill inaktivera ditt konto? Detta är oåterkalleligt.", "Confirm account deactivation": "Bekräfta kontoinaktivering", - "Security & privacy": "Säkerhet & sekretess" + "Security & privacy": "Säkerhet & sekretess", + "There was a problem communicating with the server. Please try again.": "Det var ett problem med att kommunicera med servern. Snälla försök igen.", + "Server did not require any authentication": "Servern behövde inte någon auktorisering", + "Verification Requests": "Verifikations Förfråga", + "Confirm to continue": "Konfirmera genom att fortsätta" } From 369287dcaf7d5b2f4f613dac8b7fea5fb5757171 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Sat, 29 Aug 2020 03:50:03 +0000 Subject: [PATCH 047/286] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2358 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index 27eb560442..ad4020b845 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2445,5 +2445,12 @@ "An image will help people identify your community.": "圖片可以協助人們辨識您的社群。", "Create community": "建立社群", "Explore community rooms": "探索社群聊天室", - "Create a room in %(communityName)s": "在 %(communityName)s 中建立聊天室" + "Create a room in %(communityName)s": "在 %(communityName)s 中建立聊天室", + "Cross-signing and secret storage are ready for use.": "交叉簽章與秘密儲存空間已可使用。", + "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "交叉簽章已準備好使用,但目前未使用秘密儲存空間備份您的金鑰。", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "私人聊天室僅能透過邀請找到與加入。公開聊天室則任何人都可以找到並加入。", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "私人聊天室僅能透過邀請找到與加入。公開聊天室則任何在此社群的人都可以找到並加入。", + "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "如果聊天室僅用於與在您的家伺服器上的內部團隊協作的話,可以啟用此功能。這無法在稍後變更。", + "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "如果聊天室會用於與有自己的家伺服器的外部團隊協作的話,可以停用此功能。這無法在稍後變更。", + "Block anyone not part of %(serverName)s from ever joining this room.": "阻止任何不屬於 %(serverName)s 的人加入此聊天室。" } From 053510ea9c1c61de4743ed4b656fb0bfadae17a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Fri, 28 Aug 2020 20:07:57 +0000 Subject: [PATCH 048/286] Translated using Weblate (Estonian) Currently translated at 100.0% (2358 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/et/ --- src/i18n/strings/et.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index f13c28ca3f..a2b0bb2339 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -2444,5 +2444,10 @@ "Explore community rooms": "Sirvi kogukonna jututubasid", "Create a room in %(communityName)s": "Loo uus jututuba %(communityName)s kogukonda", "Cross-signing and secret storage are ready for use.": "Risttunnustamine ja turvahoidla on kasutamiseks valmis.", - "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "Risttunnustamine on kasutamiseks valmis, kuid turvahoidla ei ole hetkel krüptovõtmete varundamiseks kasutusel." + "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "Risttunnustamine on kasutamiseks valmis, kuid turvahoidla ei ole hetkel krüptovõtmete varundamiseks kasutusel.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "Omavahelisi jututubasid on võimalik leida ning nendega liituda vaid kutse alusel. Avalikke jututubasid saavad kõik leida ning nendega liituda.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Omavahelisi jututubasid on võimalik leida ning nendega liituda vaid kutse alusel. Selles kogukonnas saavad avalikke jututubasid kõik leida ning nendega liituda.", + "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Sa võid sellise võimaluse kasutusele võtta, kui seda jututuba kasutatakse vaid organisatsioonisiseste tiimide ühistööks oma koduserveri piires. Seda ei saa hiljem muuta.", + "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Sa võid sellise võimaluse jätta kasutusele võtmata, kui seda jututuba kasutatakse erinevate väliste tiimide ühistööks kasutades erinevaid koduservereid. Seda ei saa hiljem muuta.", + "Block anyone not part of %(serverName)s from ever joining this room.": "Keela kõikide niisuguste kasutajate liitumine selle jututoaga, kelle kasutajakonto ei asu %(serverName)s koduserveris." } From 5533d2cc2353585804f471ab52c37a6d869aa13c Mon Sep 17 00:00:00 2001 From: Boo Teille Date: Mon, 31 Aug 2020 14:03:09 +0000 Subject: [PATCH 049/286] Translated using Weblate (French) Currently translated at 97.7% (2303 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/fr/ --- src/i18n/strings/fr.json | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index 42f9d74502..42eead2057 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -2377,5 +2377,23 @@ "%(brand)s Web": "%(brand)s Web", "%(brand)s Desktop": "%(brand)s Desktop", "%(brand)s iOS": "%(brand)s iOS", - "%(brand)s X for Android": "%(brand)s X pour Android" + "%(brand)s X for Android": "%(brand)s X pour Android", + "Are you sure you want to cancel entering passphrase?": "Souhaitez-vous vraiment annuler l'entrée de la phrase de passe ?", + "Unexpected server error trying to leave the room": "Erreur de serveur inattendue en essayant de quitter le salon", + "Error leaving room": "Erreur en essayant de quitter le salon", + "The person who invited you already left the room.": "La personne vous ayant invité a déjà quitté le salon.", + "The person who invited you already left the room, or their server is offline.": "La personne vous ayant invité a déjà quitté le salon, ou son serveur est hors-ligne.", + "* %(senderName)s %(emote)s": "* %(senderName)s %(emote)s", + "Change notification settings": "Modifier les paramètres de notification", + "Show message previews for reactions in DMs": "Afficher la prévisualisation des messages pour les réactions dans les messages privés", + "Show message previews for reactions in all rooms": "Afficher la prévisualisation des messages pour les réactions dans tous les salons", + "Enable advanced debugging for the room list": "Activer le débogage avancé pour la liste de salons", + "Uploading logs": "Téléversement des journaux", + "Downloading logs": "Téléchargement des journaux", + "Your server isn't responding to some requests.": "Votre serveur ne répond pas à certaines requêtes.", + "Cross-signing and secret storage are ready for use.": "La signature croisée et le coffre secret sont prêt à l'emploi.", + "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "La signature croisée est prête à l'emploi, mais le coffre secret n'est pas actuellement utilisé pour sauvegarder vos clés.", + "Master private key:": "Clé privée maîtresse :", + "%(brand)s can't securely cache encrypted messages locally while running in a web browser. Use %(brand)s Desktop for encrypted messages to appear in search results.": "%(brand)s ne peut actuellement mettre en cache vos messages chiffrés localement de manière sécurisée via le navigateur Web. Utilisez %(brand)s Desktop pour que les messages chiffrés apparaissent dans vos résultats de recherche.", + "There are advanced notifications which are not shown here.": "Des notifications avancées ne sont pas affichées ici." } From 57bdb8902743c5941d69856bfd7abc3d211b31f6 Mon Sep 17 00:00:00 2001 From: XoseM Date: Sun, 30 Aug 2020 06:32:55 +0000 Subject: [PATCH 050/286] Translated using Weblate (Galician) Currently translated at 100.0% (2358 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/gl/ --- src/i18n/strings/gl.json | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index 0f2c83fd55..0348203887 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -1884,7 +1884,7 @@ "Message layout": "Disposición da mensaxe", "Compact": "Compacta", "Modern": "Moderna", - "Power level": "Poderío", + "Power level": "Nivel de permisos", "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...", @@ -2425,5 +2425,29 @@ "Error leaving room": "Erro ó saír da sala", "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Prototipos de Comunidades v2. Require un servidor compatible. Característica experimental - usa con tino.", "Explore rooms in %(communityName)s": "Explorar salas en %(communityName)s", - "Set up Secure Backup": "Configurar Copia de apoio Segura" + "Set up Secure Backup": "Configurar Copia de apoio Segura", + "Cross-signing and secret storage are ready for use.": "A Sinatura-Cruzada e o almacenaxe segredo están listos para usar.", + "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "A Sinatura-Cruzada está preparada para usala, mais o almacenaxe segredo aínda non foi usado para facer copia das chaves.", + "Explore community rooms": "Explorar salas da comunidade", + "Information": "Información", + "Add another email": "Engadir outro email", + "People you know on %(brand)s": "Persoas que coñeces en %(brand)s", + "Send %(count)s invites|other": "Enviar %(count)s convites", + "Send %(count)s invites|one": "Enviar %(count)s convite", + "Invite people to join %(communityName)s": "Convida a persoas a unirse a %(communityName)s", + "There was an error creating your community. The name may be taken or the server is unable to process your request.": "Algo fallou ó crear a túa comunidade. O nome podería estar pillado ou o servidor non pode procesar a túa solicitude.", + "Community ID: +:%(domain)s": "ID da comunidade: +:%(domain)s", + "Use this when referencing your community to others. The community ID cannot be changed.": "Usa esto cando queiras falar sobre a túa comunidade. O ID da comunidade non se pode cambiar.", + "You can change this later if needed.": "Podes cambiar esto máis tarde se o precisas.", + "What's the name of your community or team?": "¿Cómo se chama a túa comunidade ou equipo?", + "Enter name": "Escribe o nome", + "Add image (optional)": "Engade unha imaxe (optativo)", + "An image will help people identify your community.": "Unha imaxe axudaralle á xente a identificar a túa comunidade.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "As salas privadas só se poden atopar e unirse por convite. As salas públicas son accesibles para calquera.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "As salas privadas só poden ser atopadas e unirse por convite. As salas públicas son accesibles para calquera nesta comunidade.", + "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Pode resultar útil se a sala vai ser utilizada só polo equipo de xestión interna do servidor. Non se pode cambiar máis tarde.", + "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Poderías desactivalo se a sala vai ser utilizada para colaborar con equipos externos que teñen o seu propio servidor. Esto non se pode cambiar máis tarde.", + "Create a room in %(communityName)s": "Crear unha sala en %(communityName)s", + "Block anyone not part of %(serverName)s from ever joining this room.": "Evitar que calquera externo a %(serverName)s se poida unir a esta sala.", + "Create community": "Crear comunidade" } From 71095e1a94cc1a8256af8cf179c5ea9d6bc56e8d Mon Sep 17 00:00:00 2001 From: Michael Albert Date: Mon, 31 Aug 2020 16:47:19 +0000 Subject: [PATCH 051/286] Translated using Weblate (German) Currently translated at 99.5% (2346 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 59ecf9e250..c403724f88 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -889,12 +889,12 @@ "Go back to set it again.": "Gehe zurück und setze es erneut.", "Download": "Herunterladen", "Print it and store it somewhere safe": "Drucke ihn aus und lagere ihn an einem sicheren Ort", - "Save it on a USB key or backup drive": "Speichere ihn auf einem USB-Schlüssel oder Sicherungsslaufwerk", + "Save it on a USB key or backup drive": "Speichere ihn auf einem USB-Schlüssel oder Sicherungslaufwerk", "Copy it to your personal cloud storage": "Kopiere ihn in deinen persönlichen Cloud-Speicher", "Unable to create key backup": "Konnte Schlüsselsicherung nicht erstellen", "Retry": "Erneut probieren", - "Unable to restore backup": "Konnte Sicherung nicht wiederherstellen", - "No backup found!": "Keine Sicherung gefunden!", + "Unable to restore backup": "Konnte Schlüsselsicherung nicht wiederherstellen", + "No backup found!": "Keine Schlüsselsicherung gefunden!", "This looks like a valid recovery key!": "Dies sieht wie ein gültiger Wiederherstellungsschlüssel aus!", "Not a valid recovery key": "Kein valider Wiederherstellungsschlüssel", "There was an error joining the room": "Es gab einen Fehler beim Raum-Beitreten", @@ -934,7 +934,7 @@ "Use a longer keyboard pattern with more turns": "Nutze ein längeres Tastaturmuster mit mehr Abwechslung", "Straight rows of keys are easy to guess": "Gerade Reihen von Tasten sind einfach zu erraten", "Custom user status messages": "Angepasste Nutzerstatus-Nachrichten", - "Unable to load key backup status": "Konnte Status des Schlüsselbackups nicht laden", + "Unable to load key backup status": "Konnte Status der Schlüsselsicherung nicht laden", "Don't ask again": "Nicht erneut fragen", "Set up": "Einrichten", "Please review and accept all of the homeserver's policies": "Bitte prüfen und akzeptieren Sie alle Richtlinien des Heimservers", @@ -942,7 +942,7 @@ "That doesn't look like a valid email address": "Sieht nicht nach einer validen E-Mail-Adresse aus", "Unable to load commit detail: %(msg)s": "Konnte Commit-Details nicht laden: %(msg)s", "Checking...": "Überprüfe...", - "Unable to load backup status": "Konnte Backupstatus nicht laden", + "Unable to load backup status": "Konnte Sicherungsstatus nicht laden", "Failed to decrypt %(failedCount)s sessions!": "Konnte %(failedCount)s Sitzungen nicht entschlüsseln!", "Access your secure message history and set up secure messaging by entering your recovery passphrase.": "Greifen Sie auf Ihre sichere Nachrichtenhistorie zu und richten Sie einen sicheren Nachrichtenversand ein, indem Sie Ihre Wiederherstellungspassphrase eingeben.", "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options": "Wenn du deinen Wiederherstellungspassphrase vergessen hast, kannst du deinen Wiederherstellungsschlüssel benutzen oder neue Wiederherstellungsoptionen einrichten", @@ -1128,7 +1128,7 @@ "Back up your keys before signing out to avoid losing them.": "Sichere deine Schlüssel bevor du dich abmeldest, damit du sie nicht verlierst.", "Start using Key Backup": "Beginne Schlüsselsicherung zu nutzen", "Credits": "Danksagungen", - "Starting backup...": "Starte Backup...", + "Starting backup...": "Starte Sicherung...", "Success!": "Erfolgreich!", "Your keys are being backed up (the first backup could take a few minutes).": "Deine Schlüssel werden gesichert (Das erste Backup könnte ein paar Minuten in Anspruch nehmen).", "Voice & Video": "Sprach- & Videoanruf", @@ -1384,7 +1384,7 @@ "Cannot connect to integration manager": "Verbindung zum Integrationsmanager fehlgeschlagen", "The integration manager is offline or it cannot reach your homeserver.": "Der Integrationsmanager ist offline oder er kann den Heimserver nicht erreichen.", "not stored": "nicht gespeichert", - "Backup has a signature from unknown user with ID %(deviceId)s": "Backup hat eine Signatur von Unbekanntem Nutzer mit ID %(deviceId)s", + "Backup has a signature from unknown user with ID %(deviceId)s": "Die Sicherung hat eine Signatur von Unbekanntem Nutzer mit ID %(deviceId)s", "Backup key stored: ": "Backup Schlüssel gespeichert: ", "Clear notifications": "Benachrichtigungen löschen", "Disconnect from the identity server and connect to instead?": "Verbindung vom Identitätsserver trennen und stattdessen zu verbinden?", @@ -2094,7 +2094,7 @@ "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.": "Ein Widget unter %(widgetUrl)s möchte deine Identität überprüfen. Wenn du dies zulässt, kann das Widget deine Nutzer-ID überprüfen, jedoch keine Aktionen in deinem Namen ausführen.", "Unable to access secret storage. Please verify that you entered the correct recovery passphrase.": "Der sichere Speicher konnte nicht geladen werden. Bitte stelle sicher dass du die richtige Wiederherstellungspassphrase eingegeben hast.", "Backup could not be decrypted with this recovery key: please verify that you entered the correct recovery key.": "Die Sicherung konnte nicht mit dem angegebenen Wiederherstellungsschlüssel entschlüsselt werden: Bitte überprüfe ob du den richtigen Wiederherstellungsschlüssel eingegeben hast.", - "Backup could not be decrypted with this recovery passphrase: please verify that you entered the correct recovery passphrase.": "Die Sicherung konnte mit diesem Wiederherstellungsschlüssel nicht entschlüsselt werden: Bitte überprüfe ob du den richtigen Wiederherstellungspassphrase eingegeben hast.", + "Backup could not be decrypted with this recovery passphrase: please verify that you entered the correct recovery passphrase.": "Die Sicherung konnte mit diesem Wiederherstellungsschlüssel nicht entschlüsselt werden: Bitte überprüfe ob du die richtige Wiederherstellungspassphrase eingegeben hast.", "Nice, strong password!": "Super, ein starkes Passwort!", "Other users can invite you to rooms using your contact details": "Andere Benutzer können dich mit deinen Kontaktdaten in Räume einladen", "Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.": "Lege eine E-Mail für die Kontowiederherstellung fest. Verwende optional E-Mail oder Telefon, um von Anderen gefunden zu werden.", @@ -2423,7 +2423,7 @@ "Error leaving room": "Fehler beim Verlassen des Raums", "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Communities v2 Prototyp. Benötigt einen kompatiblen Heimserver. Höchst experimentell - mit Vorsicht verwenden.", "Explore rooms in %(communityName)s": "Erkunde Räume in %(communityName)s", - "Set up Secure Backup": "Sicherung einrichten", + "Set up Secure Backup": "Schlüsselsicherung einrichten", "Information": "Information", "Add another email": "Weitere E-Mail-Adresse hinzufügen", "Send %(count)s invites|other": "%(count)s Einladungen senden", From e96b4112e3645ce8bab8e308582e65af4a7e1215 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Sun, 30 Aug 2020 18:12:03 +0000 Subject: [PATCH 052/286] Translated using Weblate (Hungarian) Currently translated at 100.0% (2358 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/hu/ --- src/i18n/strings/hu.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index dea22e8b3b..bcdabf7b30 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -2442,5 +2442,12 @@ "Create community": "Közösség létrehozása", "Set up Secure Backup": "Biztonsági mentés beállítása", "Explore community rooms": "Fedezd fel a közösségi szobákat", - "Create a room in %(communityName)s": "Készíts szobát itt: %(communityName)s" + "Create a room in %(communityName)s": "Készíts szobát itt: %(communityName)s", + "Cross-signing and secret storage are ready for use.": "Az eszközök közti hitelesítés és a biztonsági tároló kész a használatra.", + "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "Az eszközök közti hitelesítés kész a használatra, de a biztonsági tároló nincs használva a kulcsok mentéséhez.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "Privát szobák csak meghívóval találhatók meg és meghívóval lehet belépni. A nyilvános szobákat bárki megtalálhatja és be is léphet.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Privát szobák csak meghívóval találhatók meg és meghívóval lehet belépni. A nyilvános szobákat a közösség bármely tagja megtalálhatja és be is léphet.", + "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Beállíthatod, ha a szobát csak egy belső csoport használja majd a matrix szervereden. Ezt később nem lehet megváltoztatni.", + "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Ne engedélyezd ezt, ha a szobát külső csapat is használja másik matrix szerverről. Később nem lehet megváltoztatni.", + "Block anyone not part of %(serverName)s from ever joining this room.": "A szobába való belépés megtagadása mindenkitől ki nem ezt a matrix szervert használja: %(serverName)s." } From 69ea555b0712944bd7b895da61bbecec843c1740 Mon Sep 17 00:00:00 2001 From: random Date: Mon, 31 Aug 2020 13:05:18 +0000 Subject: [PATCH 053/286] Translated using Weblate (Italian) Currently translated at 100.0% (2358 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/it/ --- src/i18n/strings/it.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index d639d19e24..48e2b2df20 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -2447,5 +2447,10 @@ "Create a room in %(communityName)s": "Crea una stanza in %(communityName)s", "Explore rooms in %(communityName)s": "Esplora le stanze in %(communityName)s", "Create community": "Crea comunità", - "Set up Secure Backup": "Imposta il Backup Sicuro" + "Set up Secure Backup": "Imposta il Backup Sicuro", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "Le stanze private possono essere trovate e visitate solo con invito. Le stanze pubbliche invece sono aperte a tutti.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Le stanze private possono essere trovate e visitate solo con invito. Le stanze pubbliche invece sono aperte a tutti i membri di questa comunità.", + "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Dovresti attivarlo se questa stanza verrà usata solo per collaborazioni tra squadre interne nel tuo homeserver. Non può essere cambiato in seguito.", + "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Dovresti disattivarlo se questa stanza verrà usata per collaborazioni con squadre esterne che hanno il loro homeserver. Non può essere cambiato in seguito.", + "Block anyone not part of %(serverName)s from ever joining this room.": "Blocca l'accesso alla stanza per chiunque non faccia parte di %(serverName)s." } From 50c4d7a91e05e875fccb80cb48dbb9f6f255d95b Mon Sep 17 00:00:00 2001 From: ziriSut Date: Fri, 28 Aug 2020 17:21:47 +0000 Subject: [PATCH 054/286] Translated using Weblate (Kabyle) Currently translated at 100.0% (2358 of 2358 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/kab/ --- src/i18n/strings/kab.json | 40 ++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/src/i18n/strings/kab.json b/src/i18n/strings/kab.json index ad2e683faa..fae9a1476e 100644 --- a/src/i18n/strings/kab.json +++ b/src/i18n/strings/kab.json @@ -1592,7 +1592,7 @@ "Your homeserver has exceeded one of its resource limits.": "Aqeddac-inek·inem agejdan iɛedda yiwet seg tlisa-ines tiɣbula.", "Contact your server admin.": "Nermes anedbal-inek·inem n uqeddac.", "To return to your account in future you need to set a password": "Akken ad tuɣaleḍ ɣer umiḍan-ik·im ɣer sdat tesriḍ ad tesbaduḍ awal uffir", - "New spinner design": "Afeṣṣel amaynut n tuzzya ", + "New spinner design": "Afeṣṣel amaynut n tuzzya", "Render simple counters in room header": "Err amsiḍen afessa ɣef uqerru n texxamt", "Multiple integration managers": "Imsefrak n waṭas n yimsidaf", "Try out new ways to ignore people (experimental)": "Ɛreḍ iberdan-nniḍen i tigtin n yimdanen (armitan)", @@ -1877,7 +1877,7 @@ "Show previews/thumbnails for images": "Sken tiskanin/tinfulin i tugniwin", "How fast should messages be downloaded.": "Acḥal i ilaq ad yili urured i wakken ad d-adren yiznan.", "Enable experimental, compact IRC style layout": "Rmed aseflu n uɣanib n IRC armitan, ussid", - "Waiting for %(displayName)s to verify…": "Aṛaǧu n %(displayName)s i usenqed...", + "Waiting for %(displayName)s to verify…": "Aṛaǧu n %(displayName)s i usenqed…", "Securely cache encrypted messages locally for them to appear in search results, using ": "Ḥrez iznan iwgelhanen idiganen s wudem awurman i wakken ad d-banen deg yigmaḍ n unadi, s useqdec ", "Securely cache encrypted messages locally for them to appear in search results.": "Ḥrez iznan iwgelhanen idiganen s wudem awurman i wakken ad d-banen deg yigmaḍ n unadi.", "The integration manager is offline or it cannot reach your homeserver.": "Amsefrak n umsidef ha-t-an beṛṛa n tuqqna neɣ ur yezmir ara ad yaweḍ ɣer uqeddac-ik·im agejdan.", @@ -2003,12 +2003,12 @@ "Read Marker off-screen lifetime (ms)": "Ɣer tanzagt n tudert n tecreḍt beṛṛa n ugdil (ms)", "Unignore": "Ur yettwazgel ara", "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Anedbal-ik·im n uqeddac issens awgelhen seg yixef ɣer yixef s wudem amezwer deg texxamin tusligin & yiznan usriden.", - "Manage the names of and sign out of your sessions below or verify them in your User Profile.": "Sefrek ismawen syen ffeɣ seg tɣimiyin-ik·im ddaw neɣ senqed-itent deg umaɣnu-ik·im n useqdac.", + "Manage the names of and sign out of your sessions below or verify them in your User Profile.": "Sefrek ismawen syen ffeɣ seg tɣimiyin-ik·im ddaw neɣ senqed-itent deg umaɣnu-ik·im n useqdac.", "A session's public name is visible to people you communicate with": "Isem n tiɣimit tazayezt yettban i yimdanen wukud tettmeslayeḍ", "%(brand)s collects anonymous analytics to allow us to improve the application.": "%(brand)s ileqqeḍ tasleḍt tudrigt i wakken ad aɣ-iɛawen ad nesnerni asnas.", "You have ignored this user, so their message is hidden. Show anyways.": "Tzegleḍ useqdac-a, ihi iznan-ines ffren. Ɣas akken sken-iten-id.", "You cancelled verifying %(name)s": "Tesfesxeḍ asenqed n %(name)s", - "Declining …": "Tigtin...", + "Declining …": "Tigtin…", "You sent a verification request": "Tuzneḍ asuter n usenqed", "Error decrypting video": "Tuccḍa deg uwgelhen n tvidyut", "Reactions": "Tisedmirin", @@ -2109,7 +2109,7 @@ "'%(groupId)s' is not a valid community ID": "'%(groupId)s' mačči d asulay n temɣiwent ameɣtu", "Use bots, bridges, widgets and sticker packs": "Seqdec abuten, tileggiyin, iwiǧiten d tɣawsiwin n umyintaḍ", "To continue you need to accept the terms of this service.": "I wakken ad tkemmleḍ tesriḍ ad tqebleḍ tiwtilin n umeẓlu-a.", - "This file is too large to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.": "Afaylu-a ɣezzif aṭas i wakken ad d-yali. Talast n teɣzi n ufaylu d %(limit)s.", + "This file is too large to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.": "Afaylu-a ɣezzif aṭas i wakken ad d-yali. Talast n teɣzi n ufaylu d %(limit)s maca afaylu-a d %(sizeOfThisFile)s.", "These files are too large to upload. The file size limit is %(limit)s.": "Ifuyla-a ɣezzifit aṭas i wakken ad d-alin. Talast n teɣzi n ufaylu d %(limit)s.", "Some files are too large to be uploaded. The file size limit is %(limit)s.": "Kra n yifuyla ɣezzifit aṭas i wakken ad d-alin. Talast n teɣzi n ufaylu d %(limit)s.", "Upload %(count)s other files|other": "Sali-d %(count)s ifuyla-nniḍen", @@ -2119,7 +2119,7 @@ "Remember my selection for this widget": "Cfu ɣef tefrant-inu i uwiǧit-a", "Wrong file type": "Anaw n yifuyla d arameɣtu", "Looks good!": "Yettban igerrez!", - "Enter your Security Phrase or to continue.": "Sekcem tafyirt-ik·im n tɣellist neɣ to continue.": "Sekcem tafyirt-ik·im n tɣellist neɣ
- + { this._getMembershipSection() } { this._getGroupSection() } diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index e427eb92cb..33e7c4a238 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -257,6 +257,12 @@ class LoggedInView extends React.Component { window.localStorage.setItem("mx_lhs_size", '' + size); this.props.resizeNotifier.notifyLeftHandleResized(); }, + onResizeStart: () => { + this.props.resizeNotifier.startResizing(); + }, + onResizeStop: () => { + this.props.resizeNotifier.stopResizing(); + }, }; const resizer = new Resizer( this._resizeContainer.current, @@ -650,12 +656,13 @@ class LoggedInView extends React.Component { break; case PageTypes.UserView: - pageElement = ; + pageElement = ; break; case PageTypes.GroupView: pageElement = ; break; } diff --git a/src/components/structures/MainSplit.js b/src/components/structures/MainSplit.js index 800ed76bb9..47dfe83ad6 100644 --- a/src/components/structures/MainSplit.js +++ b/src/components/structures/MainSplit.js @@ -19,9 +19,18 @@ import React from 'react'; import { Resizable } from 're-resizable'; export default class MainSplit extends React.Component { - _onResized = (event, direction, refToElement, delta) => { + _onResizeStart = () => { + this.props.resizeNotifier.startResizing(); + }; + + _onResize = () => { + this.props.resizeNotifier.notifyRightHandleResized(); + }; + + _onResizeStop = (event, direction, refToElement, delta) => { + this.props.resizeNotifier.stopResizing(); window.localStorage.setItem("mx_rhs_size", this._loadSidePanelSize().width + delta.width); - } + }; _loadSidePanelSize() { let rhsSize = parseInt(window.localStorage.getItem("mx_rhs_size"), 10); @@ -58,7 +67,9 @@ export default class MainSplit extends React.Component { bottomLeft: false, topLeft: false, }} - onResizeStop={this._onResized} + onResizeStart={this._onResizeStart} + onResize={this._onResize} + onResizeStop={this._onResizeStop} className="mx_RightPanel_ResizeWrapper" handleClasses={{left: "mx_RightPanel_ResizeHandle"}} > diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 33df9b4961..2e06be7b4c 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -2096,10 +2096,7 @@ export default createReactClass({ onLeaveClick={(myMembership === "join") ? this.onLeaveClick : null} e2eStatus={this.state.e2eStatus} /> - +
{auxPanel}
diff --git a/src/components/structures/ScrollPanel.js b/src/components/structures/ScrollPanel.js index 5c3067c6eb..1e34e7e2ff 100644 --- a/src/components/structures/ScrollPanel.js +++ b/src/components/structures/ScrollPanel.js @@ -166,7 +166,7 @@ export default createReactClass({ this._pendingFillRequests = {b: null, f: null}; if (this.props.resizeNotifier) { - this.props.resizeNotifier.on("middlePanelResized", this.onResize); + this.props.resizeNotifier.on("middlePanelResizedNoisy", this.onResize); } this.resetScrollState(); @@ -196,11 +196,12 @@ export default createReactClass({ this.unmounted = true; if (this.props.resizeNotifier) { - this.props.resizeNotifier.removeListener("middlePanelResized", this.onResize); + this.props.resizeNotifier.removeListener("middlePanelResizedNoisy", this.onResize); } }, onScroll: function(ev) { + if (this.props.resizeNotifier.isResizing) return; // skip scroll events caused by resizing debuglog("onScroll", this._getScrollNode().scrollTop); this._scrollTimeout.restart(); this._saveScrollState(); diff --git a/src/components/structures/UserView.js b/src/components/structures/UserView.js index 694592af88..8e21771bb9 100644 --- a/src/components/structures/UserView.js +++ b/src/components/structures/UserView.js @@ -80,7 +80,9 @@ export default class UserView extends React.Component { const RightPanel = sdk.getComponent('structures.RightPanel'); const MainSplit = sdk.getComponent('structures.MainSplit'); const panel = ; - return (); + return ( + + ); } else { return (
); } diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index 7124d908de..feda2e8db1 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -252,6 +252,7 @@ const PersistentVResizer = ({ maxHeight={maxHeight} onResizeStart={() => { if (!resizing) setResizing(true); + resizeNotifier.startResizing(); }} onResize={() => { resizeNotifier.notifyTimelineHeightChanged(); @@ -259,7 +260,7 @@ const PersistentVResizer = ({ onResizeStop={(e, dir, ref, d) => { setHeight(height + d.height); if (resizing) setResizing(false); - resizeNotifier.notifyTimelineHeightChanged(); + resizeNotifier.stopResizing(); }} handleWrapperClass={handleWrapperClass} handleClasses={{bottom: handleClass}} diff --git a/src/resizer/resizer.js b/src/resizer/resizer.js index 2234fc5bdf..1e75bf3bdf 100644 --- a/src/resizer/resizer.js +++ b/src/resizer/resizer.js @@ -105,6 +105,9 @@ export default class Resizer { if (this.classNames.resizing) { this.container.classList.add(this.classNames.resizing); } + if (this.config.onResizeStart) { + this.config.onResizeStart(); + } const {sizer, distributor} = this._createSizerAndDistributor(resizeHandle); distributor.start(); @@ -119,6 +122,9 @@ export default class Resizer { if (this.classNames.resizing) { this.container.classList.remove(this.classNames.resizing); } + if (this.config.onResizeStop) { + this.config.onResizeStop(); + } distributor.finish(); body.removeEventListener("mouseup", finishResize, false); document.removeEventListener("mouseleave", finishResize, false); diff --git a/src/utils/ResizeNotifier.js b/src/utils/ResizeNotifier.js index 5467716576..512946828b 100644 --- a/src/utils/ResizeNotifier.js +++ b/src/utils/ResizeNotifier.js @@ -31,6 +31,19 @@ export default class ResizeNotifier extends EventEmitter { // with default options, will call fn once at first call, and then every x ms // if there was another call in that timespan this._throttledMiddlePanel = throttle(() => this.emit("middlePanelResized"), 200); + this._isResizing = false; + } + + get isResizing() { + return this._isResizing; + } + + startResizing() { + this._isResizing = true; + } + + stopResizing() { + this._isResizing = false; } _noisyMiddlePanel() { From 156291d151bcb44dbf91be810266bb979cda08d3 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Tue, 1 Sep 2020 19:08:16 +0000 Subject: [PATCH 070/286] Translated using Weblate (Swedish) Currently translated at 90.1% (2113 of 2346 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 48 ++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 1a2194aec0..8d0bee60e6 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -172,7 +172,7 @@ "Passwords can't be empty": "Lösenorden kan inte vara tomma", "Permissions": "Behörigheter", "Phone": "Telefon", - "Please check your email and click on the link it contains. Once this is done, click continue.": "Öppna meddelandet i din epost och klicka på länken i meddelandet. När du har gjort detta, klicka vidare.", + "Please check your email and click on the link it contains. Once this is done, click continue.": "Öppna meddelandet i din e-post och klicka på länken i meddelandet. När du har gjort detta, klicka fortsätt.", "Power level must be positive integer.": "Behörighetsnivå måste vara ett positivt heltal.", "Private Chat": "Privatchatt", "Privileged Users": "Privilegierade användare", @@ -384,7 +384,7 @@ "%(brand)s does not know how to join a room on this network": "%(brand)s kan inte gå med i ett rum på det här nätverket", "Mentions only": "Endast omnämnande", "Failed to remove tag %(tagName)s from room": "Det gick inte att radera etiketten %(tagName)s från rummet", - "You can now return to your account after signing out, and sign in on other devices.": "Du kan nu återgå till ditt konto efter att ha loggat ut och logga in på andra enheter.", + "You can now return to your account after signing out, and sign in on other devices.": "Du kan nu återgå till ditt konto efter att ha loggat ut, och logga in på andra enheter.", "Enable email notifications": "Aktivera e-postaviseringar", "Download this file": "Ladda ner filen", "Failed to change settings": "Misslyckades att spara inställningarna", @@ -1157,7 +1157,7 @@ "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.": "En widget på %(widgetUrl)s vill verifiera din identitet. Genom att tillåta detta kommer widgeten att kunna verifiera ditt användar-ID, men inte agera som dig.", "Remember my selection for this widget": "Kom ihåg mitt val för den här widgeten", "Deny": "Neka", - "Unable to load backup status": "Det går inte att ladda backupstatus", + "Unable to load backup status": "Kunde inte ladda status för säkerhetskopia", "Guest": "Gäst", "Could not load user profile": "Kunde inte ladda användarprofil", "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "Om du använder 'brödsmulor' eller inte (avatarer ovanför rumslistan)", @@ -1193,7 +1193,7 @@ "Room Settings - %(roomName)s": "Rumsinställningar - %(roomName)s", "Sign out and remove encryption keys?": "Logga ut och ta bort krypteringsnycklar?", "A username can only contain lower case letters, numbers and '=_-./'": "Ett användarnamn får endast innehålla små bokstäver, siffror och '=_-./'", - "Checking...": "Kontrollerar...", + "Checking...": "Kontrollerar…", "To help us prevent this in future, please send us logs.": "För att hjälpa oss att förhindra detta i framtiden, vänligen skicka oss loggar.", "Missing session data": "Sessionsdata saknas", "Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.": "Vissa sessionsdata, inklusive krypteringsnycklar för meddelanden, saknas. Logga ut och logga in för att åtgärda detta genom återställning av nycklarna från säkerhetskopia.", @@ -1446,8 +1446,8 @@ "Message edits": "Meddelanderedigeringar", "Preview": "Förhandsvisa", "The message you are trying to send is too large.": "Meddelandet du försöker skicka är för stort.", - "Find others by phone or email": "Hitta andra via telefon eller epost", - "Be found by phone or email": "Bli hittad via telefon eller epost", + "Find others by phone or email": "Hitta andra via telefon eller e-post", + "Be found by phone or email": "Bli hittad via telefon eller e-post", "Terms of Service": "Användarvillkor", "To continue you need to accept the terms of this service.": "För att fortsätta måste du acceptera villkoren för denna tjänst.", "Service": "Tjänst", @@ -2133,5 +2133,39 @@ "Your area is experiencing difficulties connecting to the internet.": "Ditt område upplever störningar i internetuppkopplingen.", "A connection error occurred while trying to contact the server.": "Ett fel inträffade vid försök att kontakta servern.", "The server is not configured to indicate what the problem is (CORS).": "Servern är inte inställd på att indikera vad problemet är (CORS).", - "Recent changes that have not yet been received": "Nyliga ändringar har inte mottagits än" + "Recent changes that have not yet been received": "Nyliga ändringar har inte mottagits än", + "Copy": "Kopiera", + "Command Help": "Kommandohjälp", + "Upload all": "Ladda upp alla", + "Verify other session": "Verifiera annan session", + "Verification Request": "Verifikationsförfrågan", + "Wrong file type": "Fel filtyp", + "Looks good!": "Ser bra ut!", + "Wrong Recovery Key": "Fel återställningsnyckel", + "Invalid Recovery Key": "Ogiltig återställningsnyckel", + "Security Phrase": "Säkerhetsfras", + "Unable to access secret storage. Please verify that you entered the correct recovery passphrase.": "Kunde inte komma åt hemlig lagring. Vänligen verifiera att du matade in rätt återställningslösenfras.", + "Enter your Security Phrase or to continue.": "Mata in din säkerhetsfras eller för att fortsätta.", + "Security Key": "Säkerhetsnyckel", + "Use your Security Key to continue.": "Använd din säkerhetsnyckel för att fortsätta.", + "Restoring keys from backup": "Återställer nycklar från säkerhetskopia", + "Fetching keys from server...": "Hämtar nycklar från servern…", + "%(completed)s of %(total)s keys restored": "%(completed)s av %(total)s nycklar återställda", + "Recovery key mismatch": "Återställningsnyckeln matchade inte", + "Backup could not be decrypted with this recovery key: please verify that you entered the correct recovery key.": "Säkerhetskopian kunde inte avkrypteras med den här återställningsnyckeln: vänligen verifiera att du matade in rätt återställningsnyckel.", + "Incorrect recovery passphrase": "Fel återställningslösenfras", + "Backup could not be decrypted with this recovery passphrase: please verify that you entered the correct recovery passphrase.": "Säkerhetskopian kunde inte avkrypteras med den här återställningslösenfrasen: vänligen verifiera att du matade in rätt återställningslösenfras.", + "Unable to restore backup": "Kunde inte återställa säkerhetskopia", + "No backup found!": "Ingen säkerhetskopia hittad!", + "Keys restored": "Nycklar återställda", + "Failed to decrypt %(failedCount)s sessions!": "Misslyckades att avkryptera %(failedCount)s sessioner!", + "Successfully restored %(sessionCount)s keys": "Återställde framgångsrikt %(sessionCount)s nycklar", + "Enter recovery passphrase": "Mata in återställningslösenfras", + "Warning: you should only set up key backup from a trusted computer.": "Varning: Du bör endast sätta upp nyckelsäkerhetskopiering från en betrodd dator.", + "Access your secure message history and set up secure messaging by entering your recovery passphrase.": "Kom åt din säkra meddelandehistorik och sätt upp säker kommunikation genom att skriva in din återställningslösenfras.", + "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options": "Om du har glömt din återställningslösenfras kan du använda din återställningsnyckel eller ställa in nya återställningsalternativ", + "Enter recovery key": "Skriv in återställningsnyckel", + "This looks like a valid recovery key!": "Det här ser ut som en giltig återställningsnyckel!", + "Not a valid recovery key": "Inte en giltig återställningsnyckel", + "Warning: You should only set up key backup from a trusted computer.": "Varning: Du bör endast sätta upp nyckelsäkerhetskopiering från en betrodd dator." } From 9351f949cbca0421a80ac37a169dec3f8931633e Mon Sep 17 00:00:00 2001 From: Marcelo Filho Date: Wed, 2 Sep 2020 16:15:33 +0000 Subject: [PATCH 071/286] Translated using Weblate (Portuguese (Brazil)) Currently translated at 95.8% (2248 of 2346 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/pt_BR/ --- src/i18n/strings/pt_BR.json | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/i18n/strings/pt_BR.json b/src/i18n/strings/pt_BR.json index 3a18d6ce4a..451c2aa629 100644 --- a/src/i18n/strings/pt_BR.json +++ b/src/i18n/strings/pt_BR.json @@ -332,7 +332,7 @@ "Add": "Adicionar", "Error: Problem communicating with the given homeserver.": "Erro: problema de comunicação com o Servidor de Base fornecido.", "Failed to fetch avatar URL": "Falha ao obter o link da foto de perfil", - "Home": "Início", + "Home": "Home", "The phone number entered looks invalid": "O número de telefone inserido parece ser inválido", "Uploading %(filename)s and %(count)s others|zero": "Enviando o arquivo %(filename)s", "Uploading %(filename)s and %(count)s others|one": "Enviando o arquivo %(filename)s e %(count)s outros arquivos", @@ -1700,7 +1700,7 @@ "Verification Requests": "Solicitações de confirmação", "Integrations are disabled": "As integrações estão desativadas", "Integrations not allowed": "As integrações não estão permitidas", - "End": "Fim", + "End": "End", "List options": "Opções da Lista", "Jump to first unread room.": "Ir para a primeira sala não lida.", "Jump to first invite.": "Ir para o primeiro convite.", @@ -2285,5 +2285,9 @@ "Verified": "Confirmado", "Close dialog or context menu": "Fechar caixa de diálogo ou menu", "Try scrolling up in the timeline to see if there are any earlier ones.": "Tente rolar para cima na conversa para ver se há mensagens anteriores.", - "Navigate composer history": "Ver o histórico de mensagens enviadas no campo de texto" + "Navigate composer history": "Ver o histórico de mensagens enviadas no campo de texto", + "Unexpected server error trying to leave the room": "Erro inesperado no servidor, ao tentar sair da sala", + "Error leaving room": "Erro ao sair da sala", + "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Protótipo da segunda versão das Comunidades. Requer servidor principal compatível. Altamente experimental - use com cautela.", + "Space": "Barra de espaço" } From 0aafce5ee442959f9f0db2f73566c13dbadc44f5 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Wed, 2 Sep 2020 14:43:30 +0000 Subject: [PATCH 072/286] Translated using Weblate (Swedish) Currently translated at 90.1% (2114 of 2346 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 8d0bee60e6..fc4435eeaa 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -2167,5 +2167,6 @@ "Enter recovery key": "Skriv in återställningsnyckel", "This looks like a valid recovery key!": "Det här ser ut som en giltig återställningsnyckel!", "Not a valid recovery key": "Inte en giltig återställningsnyckel", - "Warning: You should only set up key backup from a trusted computer.": "Varning: Du bör endast sätta upp nyckelsäkerhetskopiering från en betrodd dator." + "Warning: You should only set up key backup from a trusted computer.": "Varning: Du bör endast sätta upp nyckelsäkerhetskopiering från en betrodd dator.", + "Access your secure message history and set up secure messaging by entering your recovery key.": "Kom åt din säkra meddelandehistorik och sätt upp säker kommunikation genom att skriva in din återställningsnyckel." } From e3f971a3d0cddb938e50596383687d5b184a7cef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Wed, 2 Sep 2020 16:32:16 +0000 Subject: [PATCH 073/286] Translated using Weblate (Estonian) Currently translated at 100.0% (2348 of 2348 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/et/ --- src/i18n/strings/et.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index a2b0bb2339..788579ef67 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -2449,5 +2449,7 @@ "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Omavahelisi jututubasid on võimalik leida ning nendega liituda vaid kutse alusel. Selles kogukonnas saavad avalikke jututubasid kõik leida ning nendega liituda.", "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Sa võid sellise võimaluse kasutusele võtta, kui seda jututuba kasutatakse vaid organisatsioonisiseste tiimide ühistööks oma koduserveri piires. Seda ei saa hiljem muuta.", "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Sa võid sellise võimaluse jätta kasutusele võtmata, kui seda jututuba kasutatakse erinevate väliste tiimide ühistööks kasutades erinevaid koduservereid. Seda ei saa hiljem muuta.", - "Block anyone not part of %(serverName)s from ever joining this room.": "Keela kõikide niisuguste kasutajate liitumine selle jututoaga, kelle kasutajakonto ei asu %(serverName)s koduserveris." + "Block anyone not part of %(serverName)s from ever joining this room.": "Keela kõikide niisuguste kasutajate liitumine selle jututoaga, kelle kasutajakonto ei asu %(serverName)s koduserveris.", + "May include members not in %(communityName)s": "Siin võib leiduda kasutajaid, kes ei ole %(communityName)s kogukonna liikmed", + "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Alusta vestust teise osapoolega tema nime, kasutajanime (näiteks ) või e-posti aadressi alusel. Sellega aga sa ei kutsu teda %(communityName)s kogukonna liikmeks. Kui soovid kedagi kutsuda %(communityName)s kogukonda, siis vajuta siia." } From b72f0f4d923f940ae0fbc6fcd318271808e5dd7f Mon Sep 17 00:00:00 2001 From: Marcelo Filho Date: Wed, 2 Sep 2020 16:29:06 +0000 Subject: [PATCH 074/286] Translated using Weblate (Portuguese (Brazil)) Currently translated at 96.0% (2254 of 2348 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/pt_BR/ --- src/i18n/strings/pt_BR.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/pt_BR.json b/src/i18n/strings/pt_BR.json index 451c2aa629..975281aa00 100644 --- a/src/i18n/strings/pt_BR.json +++ b/src/i18n/strings/pt_BR.json @@ -2289,5 +2289,11 @@ "Unexpected server error trying to leave the room": "Erro inesperado no servidor, ao tentar sair da sala", "Error leaving room": "Erro ao sair da sala", "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Protótipo da segunda versão das Comunidades. Requer servidor principal compatível. Altamente experimental - use com cautela.", - "Space": "Barra de espaço" + "Space": "Barra de espaço", + "People you know on %(brand)s": "Pessoas que você conhece em %(brand)s", + "Show": "Mostrar", + "Send %(count)s invites|other": "Enviar %(count)s convites", + "Send %(count)s invites|one": "Enviar %(count)s convite", + "Community ID: +:%(domain)s": "ID da comunidade: +:%(domain)s", + "Enter name": "Digitar nome" } From bba4d8ec62e0d6b3c7117765ecec3f85f38c18b3 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Thu, 3 Sep 2020 05:11:31 +0000 Subject: [PATCH 075/286] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2354 of 2354 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index ad4020b845..4ec70aa8f7 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2452,5 +2452,13 @@ "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "私人聊天室僅能透過邀請找到與加入。公開聊天室則任何在此社群的人都可以找到並加入。", "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "如果聊天室僅用於與在您的家伺服器上的內部團隊協作的話,可以啟用此功能。這無法在稍後變更。", "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "如果聊天室會用於與有自己的家伺服器的外部團隊協作的話,可以停用此功能。這無法在稍後變更。", - "Block anyone not part of %(serverName)s from ever joining this room.": "阻止任何不屬於 %(serverName)s 的人加入此聊天室。" + "Block anyone not part of %(serverName)s from ever joining this room.": "阻止任何不屬於 %(serverName)s 的人加入此聊天室。", + "There was an error updating your community. The server is unable to process your request.": "更新您的社群時發生錯誤。伺服器無法處理您的請求。", + "Update community": "更新社群", + "May include members not in %(communityName)s": "可能包含不在 %(communityName)s 中的成員", + "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "使用某人的名稱、使用者名稱(如 )或電子郵件地址開始與他們對話。這不會邀請他們加入 %(communityName)s。要邀請某人加入 %(communityName)s,請點擊此處。", + "Failed to find the general chat for this community": "找不到此社群的一般聊天紀錄", + "Community settings": "社群設定", + "User settings": "使用者設定", + "Community and user menu": "社群與使用者選單" } From 31aead1527d3b3e4137e3dccae8a1b234a653f70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Wed, 2 Sep 2020 16:53:01 +0000 Subject: [PATCH 076/286] Translated using Weblate (Estonian) Currently translated at 100.0% (2354 of 2354 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/et/ --- src/i18n/strings/et.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index 788579ef67..c617470601 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -2451,5 +2451,11 @@ "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Sa võid sellise võimaluse jätta kasutusele võtmata, kui seda jututuba kasutatakse erinevate väliste tiimide ühistööks kasutades erinevaid koduservereid. Seda ei saa hiljem muuta.", "Block anyone not part of %(serverName)s from ever joining this room.": "Keela kõikide niisuguste kasutajate liitumine selle jututoaga, kelle kasutajakonto ei asu %(serverName)s koduserveris.", "May include members not in %(communityName)s": "Siin võib leiduda kasutajaid, kes ei ole %(communityName)s kogukonna liikmed", - "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Alusta vestust teise osapoolega tema nime, kasutajanime (näiteks ) või e-posti aadressi alusel. Sellega aga sa ei kutsu teda %(communityName)s kogukonna liikmeks. Kui soovid kedagi kutsuda %(communityName)s kogukonda, siis vajuta siia." + "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Alusta vestust teise osapoolega tema nime, kasutajanime (näiteks ) või e-posti aadressi alusel. Sellega aga sa ei kutsu teda %(communityName)s kogukonna liikmeks. Kui soovid kedagi kutsuda %(communityName)s kogukonda, siis vajuta siia.", + "There was an error updating your community. The server is unable to process your request.": "Sinu kogukonna andmete uuendamisel tekkis viga. Server ei suuda sinu päringut töödelda.", + "Update community": "Uuenda kogukonda", + "Failed to find the general chat for this community": "Ei õnnestunud tuvastada selle kogukonna üldist rühmavestlust", + "Community settings": "Kogukonna seadistused", + "User settings": "Kasutaja seadistused", + "Community and user menu": "Kogukonna ja kasutaja menüü" } From 6dafbea5ca16ef84158986cc34bae51dc5f93566 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Wed, 2 Sep 2020 18:07:48 +0000 Subject: [PATCH 077/286] Translated using Weblate (Hungarian) Currently translated at 100.0% (2354 of 2354 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/hu/ --- src/i18n/strings/hu.json | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index bcdabf7b30..a9e209c169 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -483,7 +483,7 @@ "Community ID": "Közösség azonosító", "Add rooms to the community summary": "Szobák hozzáadása a közösségi összefoglalóhoz", "Add users to the community summary": "Felhasználók hozzáadása a közösségi összefoglalóhoz", - "Failed to update community": "Közösség frissítése sikertelen", + "Failed to update community": "Közösség módosítása sikertelen", "Leave Community": "Közösség elhagyása", "Add rooms to this community": "Szobák hozzáadása ehhez a közösséghez", "%(inviter)s has invited you to join this community": "%(inviter)s meghívott ebbe a közösségbe", @@ -2449,5 +2449,13 @@ "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Privát szobák csak meghívóval találhatók meg és meghívóval lehet belépni. A nyilvános szobákat a közösség bármely tagja megtalálhatja és be is léphet.", "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Beállíthatod, ha a szobát csak egy belső csoport használja majd a matrix szervereden. Ezt később nem lehet megváltoztatni.", "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Ne engedélyezd ezt, ha a szobát külső csapat is használja másik matrix szerverről. Később nem lehet megváltoztatni.", - "Block anyone not part of %(serverName)s from ever joining this room.": "A szobába való belépés megtagadása mindenkitől ki nem ezt a matrix szervert használja: %(serverName)s." + "Block anyone not part of %(serverName)s from ever joining this room.": "A szobába való belépés megtagadása mindenkitől ki nem ezt a matrix szervert használja: %(serverName)s.", + "There was an error updating your community. The server is unable to process your request.": "A közösség módosításakor hiba történt. A szerver nem tudja feldolgozni a kérést.", + "Update community": "Közösség módosítása", + "May include members not in %(communityName)s": "Olyan tagok is lehetnek akik nincsenek ebben a közösségben: %(communityName)s", + "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Kezdj beszélgetést valakivel akár a neve, felhasználói neve (mint ) vagy az e-mail címe segítségével. %(communityName)s közösségbe nem lesznek meghívva. Ha valakit meg szeretnél hívni ide: %(communityName)s, kattints ide.", + "Failed to find the general chat for this community": "Ehhez a közösséghez nem található általános csevegés", + "Community settings": "Közösségi beállítások", + "User settings": "Felhasználói beállítások", + "Community and user menu": "Közösségi és felhasználói menü" } From cbbaae48fc8cf6167941e3c0b52b4a6847a0f554 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Wed, 2 Sep 2020 18:28:01 +0000 Subject: [PATCH 078/286] Translated using Weblate (Swedish) Currently translated at 92.4% (2176 of 2354 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 120 ++++++++++++++++++++++++++------------- 1 file changed, 82 insertions(+), 38 deletions(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index fc4435eeaa..45998f5264 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -62,7 +62,7 @@ "Disinvite": "Häv inbjudan", "Displays action": "Visar åtgärd", "Download %(text)s": "Ladda ner %(text)s", - "Email": "Epost", + "Email": "E-post", "Email address": "E-postadress", "Emoji": "Emoji", "%(senderName)s ended the call.": "%(senderName)s avslutade samtalet.", @@ -74,14 +74,14 @@ "Failed to ban user": "Misslyckades att banna användaren", "Failed to change password. Is your password correct?": "Misslyckades att byta lösenord. Är lösenordet rätt?", "Failed to change power level": "Misslyckades att ändra behörighetsnivå", - "Failed to forget room %(errCode)s": "Det gick inte att glömma bort rummet %(errCode)s", + "Failed to forget room %(errCode)s": "Misslyckades att glömma bort rummet %(errCode)s", "Failed to join room": "Misslyckades att gå med i rummet", "Failed to kick": "Misslyckades att kicka", "Failed to leave room": "Det gick inte att lämna rummet", "Failed to load timeline position": "Det gick inte att hämta positionen på tidslinjen", "Failed to mute user": "Misslyckades att tysta användaren", "Failed to reject invite": "Det gick inte att avböja inbjudan", - "Failed to reject invitation": "Det gick inte att avböja inbjudan", + "Failed to reject invitation": "Misslyckades att avböja inbjudan", "Failed to send email": "Det gick inte att skicka epost", "Failed to send request.": "Misslyckades att sända begäran.", "Failed to set display name": "Misslyckades att ange visningsnamn", @@ -237,7 +237,7 @@ "Publish this room to the public in %(domain)s's room directory?": "Publicera rummet i den offentliga rumskatalogen på %(domain)s?", "AM": "FM", "PM": "EM", - "Submit": "Lämna in", + "Submit": "Skicka in", "The maximum permitted number of widgets have already been added to this room.": "Den största tillåtna mängden widgetar har redan tillagts till rummet.", "The phone number entered looks invalid": "Det angivna telefonnumret är ogiltigt", "This email address is already in use": "Den här e-postadressen används redan", @@ -311,7 +311,7 @@ "Messages containing my display name": "Meddelanden som innehåller mitt visningsnamn", "Messages in one-to-one chats": "Meddelanden i en-till-en chattar", "Unavailable": "Otillgänglig", - "View Decrypted Source": "Visa dekrypterad källa", + "View Decrypted Source": "Visa avkrypterad källa", "Failed to update keywords": "Kunde inte uppdatera nyckelorden", "remove %(name)s from the directory.": "ta bort %(name)s från katalogen.", "Notifications on the following keywords follow rules which can’t be displayed here:": "Aviseringar för följande nyckelord följer regler som inte kan visas här:", @@ -478,10 +478,10 @@ "Failed to send logs: ": "Misslyckades att skicka loggar: ", "Submit debug logs": "Skicka felsökningsloggar", "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.": "Felsökningsloggar innehåller användningsdata för applikationen inklusive ditt användarnamn, ID:n eller alias för de rum och grupper du har besökt och användarnamn för andra användare. De innehåller inte meddelanden.", - "An email has been sent to %(emailAddress)s": "Ett epostmeddelande har skickats till %(emailAddress)s", - "Please check your email to continue registration.": "Vänligen kolla din epost för att fortsätta registreringen.", - "Token incorrect": "Ogiltig token", - "A text message has been sent to %(msisdn)s": "Ett textmeddelande har skickats till %(msisdn)s", + "An email has been sent to %(emailAddress)s": "Ett e-postmeddelande har skickats till %(emailAddress)s", + "Please check your email to continue registration.": "Vänligen kolla din e-post för att fortsätta registreringen.", + "Token incorrect": "Felaktig token", + "A text message has been sent to %(msisdn)s": "Ett SMS har skickats till %(msisdn)s", "Please enter the code it contains:": "Vänligen ange koden det innehåller:", "Code": "Kod", "Set a display name:": "Ange ett visningsnamn:", @@ -508,7 +508,7 @@ "Start automatically after system login": "Starta automatiskt vid systeminloggning", "This will allow you to reset your password and receive notifications.": "Det här låter dig återställa lösenordet och ta emot aviseringar.", "You have no visible notifications": "Du har inga synliga aviseringar", - "Failed to upload image": "Det gick inte att ladda upp bild", + "Failed to upload image": "Misslyckades att ladda upp bild", "New Password": "Nytt lösenord", "Do you want to set an email address?": "Vill du ange en e-postadress?", "Not a valid %(brand)s keyfile": "Inte en giltig %(brand)s-nyckelfil", @@ -537,7 +537,7 @@ "Tried to load a specific point in this room's timeline, but was unable to find it.": "Försökte ladda en specifik punkt i det här rummets tidslinje, men kunde inte hitta den.", "Success": "Framgång", "Unable to remove contact information": "Kunde inte ta bort kontaktuppgifter", - "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "Ett epostmeddelande har skickats till %(emailAddress)s. När du har öppnat länken i det, klicka nedan.", + "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "Ett e-brev har skickats till %(emailAddress)s. När du har öppnat länken i det, klicka nedan.", "Please note you are logging into the %(hs)s server, not matrix.org.": "Observera att du loggar in på %(hs)s-servern, inte matrix.org.", "This homeserver doesn't offer any login flows which are supported by this client.": "Denna hemserver erbjuder inga inloggningsflöden som stöds av den här klienten.", "Upload new:": "Ladda upp ny:", @@ -548,7 +548,7 @@ "Minimize apps": "Minimera appar", "Failed to invite the following users to %(groupId)s:": "Misslyckades att bjuda in följande användare till %(groupId)s:", "Failed to invite users to %(groupId)s": "Misslyckades att bjuda in användare till %(groupId)s", - "This room is not public. You will not be able to rejoin without an invite.": "Detta rum är inte offentligt. Du kommer inte kunna gå med igen utan en inbjudan.", + "This room is not public. You will not be able to rejoin without an invite.": "Det här rummet är inte offentligt. Du kommer inte kunna gå med igen utan en inbjudan.", "Ignores a user, hiding their messages from you": "Ignorerar en användare och döljer dess meddelanden för dig", "Stops ignoring a user, showing their messages going forward": "Slutar ignorera en användare och visar dess meddelanden framöver", "Opens the Developer Tools dialog": "Öppna dialogrutan Utvecklarverktyg", @@ -599,11 +599,11 @@ "We encountered an error trying to restore your previous session.": "Ett fel uppstod vid återställning av din tidigare session.", "If you have previously used a more recent version of %(brand)s, your session may be incompatible with this version. Close this window and return to the more recent version.": "Om du nyligen har använt en senare version av %(brand)s kan din session vara inkompatibel med den här versionen. Stäng det här fönstret och använd senare versionen istället.", "Clearing your browser's storage may fix the problem, but will sign you out and cause any encrypted chat history to become unreadable.": "Att rensa webbläsarens lagring kan lösa problemet, men då loggas du ut och krypterad chatthistorik blir oläslig.", - "Collapse Reply Thread": "Dölj svarstråd", + "Collapse Reply Thread": "Kollapsa svarstråd", "Terms and Conditions": "Villkor", "To continue using the %(homeserverDomain)s homeserver you must review and agree to our terms and conditions.": "För att fortsätta använda hemservern %(homeserverDomain)s måste du granska och godkänna våra villkor.", "Review terms and conditions": "Granska villkoren", - "Old cryptography data detected": "Gammal krypteringsdata upptäckt", + "Old cryptography data detected": "Gammal kryptografidata upptäckt", "Unable to capture screen": "Kunde inte ta skärmdump", "Failed to add the following rooms to %(groupId)s:": "Misslyckades att lägga till följande rum till %(groupId)s:", "Missing roomId.": "Rums-ID saknas.", @@ -616,20 +616,20 @@ "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Där denna sida innehåller identifierbar information, till exempel ett rums-, användar- eller grupp-ID, tas datan bort innan den skickas till servern.", "The remote side failed to pick up": "Mottagaren svarade inte", "Jump to read receipt": "Hoppa till läsindikation", - "This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.": "Denna process låter dig exportera nycklarna för meddelanden som du har fått i krypterade rum till en lokal fil. Du kommer sedan att kunna importera filen i en annan Matrix-klient i framtiden, så att den klienten också kan dekryptera meddelandena.", + "This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.": "Denna process låter dig exportera nycklarna för meddelanden som du har fått i krypterade rum till en lokal fil. Du kommer sedan att kunna importera filen i en annan Matrix-klient i framtiden, så att den klienten också kan avkryptera meddelandena.", "Unknown for %(duration)s": "Okänt i %(duration)s", "Unknown": "Okänt", "e.g. %(exampleValue)s": "t.ex. %(exampleValue)s", "Can't leave Server Notices room": "Kan inte lämna serveraviseringsrummet", "This room is used for important messages from the Homeserver, so you cannot leave it.": "Detta rum används för viktiga meddelanden från hemservern, så du kan inte lämna det.", - "Data from an older version of %(brand)s has been detected. This will have caused end-to-end cryptography to malfunction in the older version. End-to-end encrypted messages exchanged recently whilst using the older version may not be decryptable in this version. This may also cause messages exchanged with this version to fail. If you experience problems, log out and back in again. To retain message history, export and re-import your keys.": "Data från en äldre version av %(brand)s has upptäckts. Detta ska ha orsakat att totalsträckskryptering inte fungerat i den äldre versionen. Krypterade meddelanden som nyligen har skickats medans den äldre versionen användes kanske inte kan dekrypteras i denna version. Detta kan även orsaka att meddelanden skickade med denna version inte fungerar. Om du upplever problem, logga ut och in igen. För att behålla meddelandehistoriken, exportera dina nycklar och importera dem igen.", + "Data from an older version of %(brand)s has been detected. This will have caused end-to-end cryptography to malfunction in the older version. End-to-end encrypted messages exchanged recently whilst using the older version may not be decryptable in this version. This may also cause messages exchanged with this version to fail. If you experience problems, log out and back in again. To retain message history, export and re-import your keys.": "Data från en äldre version av %(brand)s has upptäckts. Detta ska ha orsakat att totalsträckskryptering inte fungerat i den äldre versionen. Krypterade meddelanden som nyligen har skickats medans den äldre versionen användes kanske inte kan avkrypteras i denna version. Detta kan även orsaka att meddelanden skickade med denna version inte fungerar. Om du upplever problem, logga ut och in igen. För att behålla meddelandehistoriken, exportera dina nycklar och importera dem igen.", "Confirm Removal": "Bekräfta borttagning", "%(severalUsers)sleft and rejoined %(count)s times|other": "%(severalUsers)slämnade och gick med igen %(count)s gånger", "%(severalUsers)sleft and rejoined %(count)s times|one": "%(severalUsers)slämnade och gick med igen", "%(oneUser)sleft and rejoined %(count)s times|other": "%(oneUser)slämnade och gick med igen %(count)s gånger", "%(oneUser)sleft and rejoined %(count)s times|one": "%(oneUser)slämnade och gick med igen", "%(severalUsers)srejected their invitations %(count)s times|other": "%(severalUsers)savböjde sina inbjudningar %(count)s gånger", - "Unable to reject invite": "Det gick inte att avböja inbjudan", + "Unable to reject invite": "Kunde inte avböja inbjudan", "Reject all %(invitedRooms)s invites": "Avböj alla %(invitedRooms)s inbjudningar", "%(severalUsers)srejected their invitations %(count)s times|one": "%(severalUsers)savböjde sina inbjudningar", "%(oneUser)srejected their invitation %(count)s times|other": "%(oneUser)savböjde sin inbjudan %(count)s gånger", @@ -696,10 +696,10 @@ "

HTML for your community's page

\n

\n Use the long description to introduce new members to the community, or distribute\n some important links\n

\n

\n You can even use 'img' tags\n

\n": "

HTML för din gemenskapssida

\n

\n Använd den långa beskrivningen för att introducera nya medlemmar till gemenskapen, eller dela\n några viktiga länkar\n

\n

\n Du kan även använda 'img'-taggar\n

\n", "Add rooms to the community summary": "Lägg till rum i gemenskapsöversikten", "Add users to the community summary": "Lägg till användare i gemenskapsöversikten", - "Failed to update community": "Det gick inte att uppdatera gemenskapen", - "Unable to join community": "Det gick inte att gå med i gemenskapen", + "Failed to update community": "Misslyckades att uppdatera gemenskapen", + "Unable to join community": "Kunde inte gå med i gemenskapen", "Leave Community": "Lämna gemenskapen", - "Unable to leave community": "Det gick inte att lämna gemenskap", + "Unable to leave community": "Kunde inte lämna gemenskapen", "Community Settings": "Gemenskapsinställningar", "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.": "Det kan dröja upp till 30 minuter innan ändringar på gemenskapens namn och avatar blir synliga för andra användare.", "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.": "Dessa rum visas för gemenskapsmedlemmar på gemenskapssidan. Gemenskapsmedlemmar kan gå med i rummen genom att klicka på dem.", @@ -710,7 +710,7 @@ "You are an administrator of this community": "Du är administratör för denna gemenskap", "You are a member of this community": "Du är medlem i denna gemenskap", "Who can join this community?": "Vem kan gå med i denna gemenskap?", - "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!": "Din gemenskap har ingen lång beskrivning eller HTML-sida att visa för medlemmar.
Klicka här för att öppna inställningarna och lägga till det!", + "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!": "Din gemenskap har ingen lång beskrivning, en HTML-sida att visa för medlemmar.
Klicka här för att öppna inställningarna och lägga till en!", "Community %(groupId)s not found": "Gemenskapen %(groupId)s hittades inte", "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.": "För att skapa ett filter, dra en gemenskapsavatar till filterpanelen längst till vänster på skärmen. Du kan när som helst klicka på en avatar i filterpanelen för att bara se rum och personer som är associerade med den gemenskapen.", "Create a new community": "Skapa en ny gemenskap", @@ -727,7 +727,7 @@ "Everyone": "Alla", "Long Description (HTML)": "Lång beskrivning (HTML)", "Description": "Beskrivning", - "Failed to load %(groupId)s": "Det gick inte att ladda %(groupId)s", + "Failed to load %(groupId)s": "Misslyckades att ladda %(groupId)s", "Failed to withdraw invitation": "Misslyckades att dra tillbaka inbjudan", "Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Är du säker på att du vill ta bort '%(roomName)s' från %(groupId)s?", "Failed to remove '%(roomName)s' from %(groupId)s": "Misslyckades att ta bort '%(roomName)s' från %(groupId)s", @@ -751,16 +751,16 @@ "URL Previews": "URL-förhandsvisning", "Which rooms would you like to add to this summary?": "Vilka rum vill du lägga till i översikten?", "Add to summary": "Lägg till i översikt", - "Failed to add the following rooms to the summary of %(groupId)s:": "Det gick inte att lägga till följande rum i översikten för %(groupId)s:", + "Failed to add the following rooms to the summary of %(groupId)s:": "Misslyckades att lägga till följande rum i översikten för %(groupId)s:", "Add a Room": "Lägg till ett rum", - "Failed to remove the room from the summary of %(groupId)s": "Det gick inte att ta bort rummet från översikten i %(groupId)s", + "Failed to remove the room from the summary of %(groupId)s": "Misslyckades att ta bort rummet från översikten i %(groupId)s", "The room '%(roomName)s' could not be removed from the summary.": "Rummet '%(roomName)s' kunde inte tas bort från översikten.", "Who would you like to add to this summary?": "Vem vill du lägga till i översikten?", - "Failed to add the following users to the summary of %(groupId)s:": "Det gick inte att lägga till följande användare i översikten för %(groupId)s:", + "Failed to add the following users to the summary of %(groupId)s:": "Misslyckades att lägga till följande användare i översikten för %(groupId)s:", "Add a User": "Lägg till en användare", - "Failed to remove a user from the summary of %(groupId)s": "Det gick inte att ta bort en användare från översikten i %(groupId)s", + "Failed to remove a user from the summary of %(groupId)s": "Misslyckades att ta bort en användare från översikten i %(groupId)s", "The user '%(displayName)s' could not be removed from the summary.": "Användaren '%(displayName)s' kunde inte tas bort från översikten.", - "Unable to accept invite": "Det gick inte att acceptera inbjudan", + "Unable to accept invite": "Kunde inte acceptera inbjudan", "Leave %(groupName)s?": "Lämna %(groupName)s?", "Enable widget screenshots on supported widgets": "Aktivera widget-skärmdumpar för widgets som stöder det", "Key request sent.": "Nyckelbegäran skickad.", @@ -776,7 +776,7 @@ "Error decrypting video": "Fel vid avkryptering av video", "Add an Integration": "Lägg till integration", "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "Du kommer att skickas till en tredjepartswebbplats så att du kan autentisera ditt konto för användning med %(integrationsUrl)s. Vill du fortsätta?", - "If you don't specify an email address, you won't be able to reset your password. Are you sure?": "Om du inte anger en epostadress, kan du inte återställa ditt lösenord. Är du säker?", + "If you don't specify an email address, you won't be able to reset your password. Are you sure?": "Om du inte anger en e-postadress, kan du inte återställa ditt lösenord. Är du säker?", "Popout widget": "Poppa ut widget", "were unbanned %(count)s times|other": "blev avbannade %(count)s gånger", "were unbanned %(count)s times|one": "blev avbannade", @@ -828,9 +828,9 @@ "You do not have permission to start a conference call in this room": "Du har inte behörighet att starta ett gruppsamtal i detta rum", "This event could not be displayed": "Den här händelsen kunde inte visas", "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.": "I krypterade rum, som detta, är URL-förhandsvisning inaktiverad som förval för att säkerställa att din hemserver (där förhandsvisningar genereras) inte kan samla information om länkar du ser i rummet.", - "The email field must not be blank.": "Epost-fältet får inte vara tomt.", - "The phone number field must not be blank.": "Telefonnummer-fältet får inte vara tomt.", - "The password field must not be blank.": "Lösenords-fältet får inte vara tomt.", + "The email field must not be blank.": "E-postfältet får inte vara tomt.", + "The phone number field must not be blank.": "Telefonnummerfältet får inte vara tomt.", + "The password field must not be blank.": "Lösenordsfältet får inte vara tomt.", "Failed to remove widget": "Misslyckades att radera widget", "An error ocurred whilst trying to remove the widget from the room": "Ett fel inträffade vid borttagning av widget från rummet", "Demote yourself?": "Degradera dig själv?", @@ -1042,7 +1042,7 @@ "Clear status": "Rensa status", "Update status": "Uppdatera status", "Set status": "Ange status", - "Set a new status...": "Ange en ny status...", + "Set a new status...": "Ange en ny status…", "Hide": "Dölj", "This homeserver would like to make sure you are not a robot.": "Denna hemserver vill se till att du inte är en robot.", "Server Name": "Servernamn", @@ -1052,16 +1052,16 @@ "Sign in to your Matrix account on %(serverName)s": "Logga in med ditt Matrix-konto på %(serverName)s", "Change": "Ändra", "Create your Matrix account on %(serverName)s": "Skapa ditt Matrix-konto på %(serverName)s", - "Email (optional)": "Epost (valfritt)", + "Email (optional)": "E-post (valfritt)", "Phone (optional)": "Telefon (valfritt)", "Confirm": "Bekräfta", "Other servers": "Andra servrar", "Homeserver URL": "Hemserver-URL", "Identity Server URL": "Identitetsserver-URL", "Free": "Gratis", - "Join millions for free on the largest public server": "Bli medlem gratis på den största offentliga servern", + "Join millions for free on the largest public server": "Gå med miljontals användare gratis på den största publika servern", "Premium": "Premium", - "Premium hosting for organisations Learn more": "Premium-hosting för organisationer Läs mer", + "Premium hosting for organisations Learn more": "Premium-servervärd för organisationer Läs mer", "Other": "Annat", "Find other public servers or use a custom server": "Hitta andra offentliga servrar eller använd en anpassad server", "Your Matrix account on %(serverName)s": "Ditt Matrix-konto på %(serverName)s", @@ -1311,7 +1311,7 @@ "edited": "redigerat", "Sign in to your Matrix account on ": "Logga in med ditt Matrix-konto på ", "Please install Chrome, Firefox, or Safari for the best experience.": "Installera Chrome, Firefox, eller Safari för den bästa upplevelsen.", - "Couldn't load page": "Det gick inte att ladda sidan", + "Couldn't load page": "Kunde inte ladda sidan", "Want more than a community? Get your own server": "Vill du ha mer än en gemenskap? Skaffa din egen server", "This homeserver does not support communities": "Denna hemserver stöder inte gemenskaper", "Explore": "Utforska", @@ -1444,7 +1444,7 @@ "Your %(brand)s doesn't allow you to use an Integration Manager to do this. Please contact an admin.": "Din %(brand)s tillåter dig inte att använda en integrationshanterare för att göra detta. Vänligen kontakta en administratör.", "Your homeserver doesn't seem to support this feature.": "Din hemserver verkar inte stödja den här funktionen.", "Message edits": "Meddelanderedigeringar", - "Preview": "Förhandsvisa", + "Preview": "Förhandsgranska", "The message you are trying to send is too large.": "Meddelandet du försöker skicka är för stort.", "Find others by phone or email": "Hitta andra via telefon eller e-post", "Be found by phone or email": "Bli hittad via telefon eller e-post", @@ -2168,5 +2168,49 @@ "This looks like a valid recovery key!": "Det här ser ut som en giltig återställningsnyckel!", "Not a valid recovery key": "Inte en giltig återställningsnyckel", "Warning: You should only set up key backup from a trusted computer.": "Varning: Du bör endast sätta upp nyckelsäkerhetskopiering från en betrodd dator.", - "Access your secure message history and set up secure messaging by entering your recovery key.": "Kom åt din säkra meddelandehistorik och sätt upp säker kommunikation genom att skriva in din återställningsnyckel." + "Access your secure message history and set up secure messaging by entering your recovery key.": "Kom åt din säkra meddelandehistorik och sätt upp säker kommunikation genom att skriva in din återställningsnyckel.", + "If you've forgotten your recovery key you can ": "Om du har glömt din återställningsnyckel så kan du ", + "Resend edit": "Skicka redigering igen", + "Resend %(unsentCount)s reaction(s)": "Skicka %(unsentCount)s reaktion(er) igen", + "Resend removal": "Skicka borttagning igen", + "Share Permalink": "Dela permalänk", + "Report Content": "Rapportera innehåll", + "This room is public": "Det här rummet är offentligt", + "Away": "Borta", + "Country Dropdown": "Land-dropdown", + "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use %(brand)s with an existing Matrix account on a different homeserver.": "Du kan använda de anpassade serveralternativen för att logga in på andra Matrix-servrar genom att ange en annan hemserver-URL. Detta gör att du kan använda %(brand)s med ett befintligt Matrix-konto på en annan hemserver.", + "Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.": "Saknar publik nyckel för captcha i hemserverns konfiguration. Vänligen rapportera detta till din hemservers administratör.", + "Please review and accept all of the homeserver's policies": "Vänligen granska och acceptera alla hemserverns policyer", + "Unable to validate homeserver/identity server": "Kunde inte validera hemserver/identitetsserver", + "Enter the location of your Element Matrix Services homeserver. It may use your own domain name or be a subdomain of element.io.": "Ange platsen för din Element Matrix Services-hemserver. Den kan använda ditt eget domännamn eller vara en underdomän till element.io.", + "Enter password": "Skriv in lösenord", + "Nice, strong password!": "Bra, säkert lösenord!", + "Password is allowed, but unsafe": "Lösenordet är tillåtet men osäkert", + "Keep going...": "Fortsätt…", + "No identity server is configured so you cannot add an email address in order to reset your password in the future.": "Ingen identitetsserver är konfigurerad så du kan inte lägga till en e-postadress för att återställa ditt lösenord i framtiden.", + "Use an email address to recover your account": "Använd en a-postadress för att återställa ditt konto", + "Enter email address (required on this homeserver)": "Skriv in e-postadress (krävs på den här hemservern)", + "Doesn't look like a valid email address": "Det ser inte ut som en giltig e-postadress", + "Passwords don't match": "Lösenorden matchar inte", + "Other users can invite you to rooms using your contact details": "Andra användare kan bjuda in dig till rum med dina kontaktuppgifter", + "Enter phone number (required on this homeserver)": "Skriv in telefonnummer (krävs på den här hemservern)", + "Doesn't look like a valid phone number": "Det ser inte ut som ett giltigt telefonnummer", + "Use lowercase letters, numbers, dashes and underscores only": "Använd endast små bokstäver, siffror, bindestreck och understreck", + "Enter username": "Skriv in användarnamn", + "Create your Matrix account on ": "Skapa ditt Matrix-konto på ", + "Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.": "Sätt en e-postadress för kontoåterställning. Använd valfritt en e-postadress eller ett telefonnummer för kunna upptäckas av existerande kontakter.", + "Set an email for account recovery. Use email to optionally be discoverable by existing contacts.": "Sätt en e-postadress för kontoåterställning. Använd valfritt en e-postadress för kunna upptäckas av existerande kontakter.", + "Enter your custom homeserver URL What does this mean?": "Skriv in din hemserver-URL Vad betyder det här?", + "Enter your custom identity server URL What does this mean?": "Skriv in din anpassade identitetsserver-URL Vad betyder det här?", + "Sign in with SSO": "Logga in med SSO", + "No files visible in this room": "Inga filer synliga i det här rummet", + "Attach files from chat or just drag and drop them anywhere in a room.": "Bifoga filer från chatten eller dra och släpp dem vart som helst i rummet.", + "Welcome to %(appName)s": "Välkommen till %(appName)s", + "Liberate your communication": "Befria din kommunikation", + "Send a Direct Message": "Skicka ett direktmeddelande", + "Explore Public Rooms": "Utforska offentliga rum", + "Create a Group Chat": "Skapa en gruppchatt", + "Explore rooms": "Utforska rum", + "Self-verification request": "Självverifieringsförfrågan", + "%(creator)s created and configured the room.": "%(creator)s skapade och konfigurerade rummet." } From 9b937ecf238155298d4888b236f242227670b1cd Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Thu, 3 Sep 2020 16:16:22 +0000 Subject: [PATCH 079/286] Translated using Weblate (Swedish) Currently translated at 92.7% (2182 of 2354 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 45998f5264..4bc5065bc2 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -2212,5 +2212,11 @@ "Create a Group Chat": "Skapa en gruppchatt", "Explore rooms": "Utforska rum", "Self-verification request": "Självverifieringsförfrågan", - "%(creator)s created and configured the room.": "%(creator)s skapade och konfigurerade rummet." + "%(creator)s created and configured the room.": "%(creator)s skapade och konfigurerade rummet.", + "You’re all caught up": "Du är ikapp", + "You have no visible notifications in this room.": "Du har inga synliga aviseringar i det här rummet.", + "%(brand)s failed to get the protocol list from the homeserver. The homeserver may be too old to support third party networks.": "%(brand)s misslyckades att hämta protokollistan från hemservern. Hemservern kan vara för gammal för att stödja tredjepartsnätverk.", + "%(brand)s failed to get the public room list.": "%(brand)s misslyckades att hämta listan över offentliga rum.", + "The homeserver may be unavailable or overloaded.": "Hemservern kan vara otillgänglig eller överbelastad.", + "Delete the room address %(alias)s and remove %(name)s from the directory?": "Radera rumsadressen %(alias)s och ta bort %(name)s från den här katalogen?" } From 5d8a082eb198ac516b71cff974f390da977c8a52 Mon Sep 17 00:00:00 2001 From: Jason Robinson Date: Fri, 4 Sep 2020 10:03:30 +0300 Subject: [PATCH 080/286] Add Jitsi auth check Checks for auth needed by looking up a well-known file from the preferred Jitsi domain. No file existing will assume no auth. --- src/widgets/Jitsi.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/widgets/Jitsi.ts b/src/widgets/Jitsi.ts index a52f8182aa..1805913ad6 100644 --- a/src/widgets/Jitsi.ts +++ b/src/widgets/Jitsi.ts @@ -34,6 +34,30 @@ export class Jitsi { return this.domain || 'jitsi.riot.im'; } + /** + * Checks for auth needed by looking up a well-known file + * + * If the file does not exist, we assume no auth. + * + * See TODO add link + */ + public async getJitsiAuth(): Promise { + if (!this.preferredDomain) { + return null; + } + let data; + try { + const response = await fetch(`https://${this.preferredDomain}/.well-known/element/jitsi`); + data = await response.json(); + } catch (error) { + return null; + } + if (data.auth) { + return data.auth; + } + return null; + } + public start() { const cli = MatrixClientPeg.get(); cli.on("WellKnown.client", this.update); From db2e1a9cd0b34a5ca700d329256794fd51c9c80d Mon Sep 17 00:00:00 2001 From: Jason Robinson Date: Fri, 4 Sep 2020 10:04:18 +0300 Subject: [PATCH 081/286] Add rfc4648 (base64/32/16) encoder to dependencies --- package.json | 1 + yarn.lock | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/package.json b/package.json index fc5ed57a77..dd293fabf4 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "react-focus-lock": "^2.4.1", "react-transition-group": "^4.4.1", "resize-observer-polyfill": "^1.5.1", + "rfc4648": "^1.4.0", "sanitize-html": "^1.27.1", "tar-js": "^0.3.0", "text-encoding-utf-8": "^1.0.2", diff --git a/yarn.lock b/yarn.lock index 5bd2be1567..ec099bbf7c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7557,6 +7557,11 @@ retry@^0.10.0: resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q= +rfc4648@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/rfc4648/-/rfc4648-1.4.0.tgz#c75b2856ad2e2d588b6ddb985d556f1f7f2a2abd" + integrity sha512-3qIzGhHlMHA6PoT6+cdPKZ+ZqtxkIvg8DZGKA5z6PQ33/uuhoJ+Ws/D/J9rXW6gXodgH8QYlz2UCl+sdUDmNIg== + rimraf@2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" From 680de2af95c4319f97e69e2c6b8d55e6df6fd342 Mon Sep 17 00:00:00 2001 From: Jason Robinson Date: Fri, 4 Sep 2020 10:05:47 +0300 Subject: [PATCH 082/286] Create Jitsi "openidtoken-jwt" auth compatible conference ID's If the Jitsi server we're using for a Jitsi conference call has auth of "openidtoken-jwt" then instead of a random human readable room ID, encode the room ID in base32 (without padding). This can then be decoded back to the room ID on the Jitsi end of things. --- src/CallHandler.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/CallHandler.js b/src/CallHandler.js index 18f6aeb98a..9d1760bf43 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -1,7 +1,7 @@ /* Copyright 2015, 2016 OpenMarket Ltd Copyright 2017, 2018 New Vector 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. @@ -67,6 +67,7 @@ import {generateHumanReadableId} from "./utils/NamingUtils"; import {Jitsi} from "./widgets/Jitsi"; import {WidgetType} from "./widgets/WidgetType"; import {SettingLevel} from "./settings/SettingLevel"; +import {base32} from "rfc4648"; global.mxCalls = { //room_id: MatrixCall @@ -388,8 +389,21 @@ async function _startCallApp(roomId, type) { return; } - const confId = `JitsiConference${generateHumanReadableId()}`; const jitsiDomain = Jitsi.getInstance().preferredDomain; + const jitsiAuth = await Jitsi.getInstance().getJitsiAuth(); + let confId; + if (jitsiAuth === 'openidtoken-jwt') { + // Create conference ID from room ID + // For compatibility with Jitsi, use base32 without padding. + // If the room ID needs to be decoded from the conference ID, + // the receiver should first uppercase it if needed and then add padding. + // More details here: + // TODO add link + confId = base32.stringify(Buffer.from(roomId), { pad: false }); + } else { + // Create a random human readable conference ID + confId = `JitsiConference${generateHumanReadableId()}`; + } let widgetUrl = WidgetUtils.getLocalJitsiWrapperUrl(); From a511ad6633cd0ba9144146845bb9a607bb48624d Mon Sep 17 00:00:00 2001 From: Jason Robinson Date: Fri, 4 Sep 2020 10:20:58 +0300 Subject: [PATCH 083/286] Add (potential) Jitsi auth type to widget data So we don't have to fetch the auth type when joining the conference. --- src/CallHandler.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/CallHandler.js b/src/CallHandler.js index 9d1760bf43..702613bb81 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -411,12 +411,14 @@ async function _startCallApp(roomId, type) { const parsedUrl = new URL(widgetUrl); parsedUrl.search = ''; // set to empty string to make the URL class use searchParams instead parsedUrl.searchParams.set('confId', confId); + parsedUrl.searchParams.set('auth', jitsiAuth); widgetUrl = parsedUrl.toString(); const widgetData = { conferenceId: confId, isAudioOnly: type === 'voice', domain: jitsiDomain, + auth: jitsiAuth, }; const widgetId = ( From 7c65c592383f9f9e22c400370165ca7f9100a553 Mon Sep 17 00:00:00 2001 From: Nikita Epifanov Date: Thu, 3 Sep 2020 21:06:43 +0000 Subject: [PATCH 084/286] Translated using Weblate (Russian) Currently translated at 100.0% (2354 of 2354 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-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 d2664a9ca5..919077af24 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -2445,5 +2445,13 @@ "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Приватные комнаты можно найти и присоединиться только по приглашению. Публичные комнаты могут находить и присоединяться к ним любые участники этого сообщества.", "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Вы можете включить это, если комната будет использоваться только для совместной работы с внутренними командами на вашем домашнем сервере. Это не может быть изменено позже.", "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Вы можете отключить это, если комната будет использоваться для совместной работы с внешними командами, у которых есть собственный домашний сервер. Это не может быть изменено позже.", - "Block anyone not part of %(serverName)s from ever joining this room.": "Запретить кому-либо, не входящему в %(serverName)s, когда-либо присоединяться к этой комнате." + "Block anyone not part of %(serverName)s from ever joining this room.": "Запретить кому-либо, не входящему в %(serverName)s, когда-либо присоединяться к этой комнате.", + "There was an error updating your community. The server is unable to process your request.": "Произошла ошибка в обновлении вашего сообщества. Сервер не может обработать ваш запрос.", + "Update community": "Обновить сообщество", + "May include members not in %(communityName)s": "Может включать участников, не входящих в %(communityName)s", + "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Начните разговор с кем-нибудь, используя имя, имя пользователя (например, ) или адрес электронной почты. Это не пригласит их в %(communityName)s. Чтобы пригласить кого-нибудь в %(communityName)s, нажмите здесь.", + "Failed to find the general chat for this community": "Не удалось найти общий чат для этого сообщества", + "Community settings": "Настройки сообщества", + "User settings": "Пользовательские настройки", + "Community and user menu": "Сообщество и меню пользователя" } From 9ff8c172ef88d08233a23e7d2bc14c3f437a18d1 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Thu, 3 Sep 2020 16:34:26 +0000 Subject: [PATCH 085/286] Translated using Weblate (Swedish) Currently translated at 93.0% (2189 of 2354 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 4bc5065bc2..eb416bb2d0 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -283,7 +283,7 @@ "You need to be able to invite users to do that.": "Du behöver kunna bjuda in användare för att göra det där.", "You are not in this room.": "Du är inte i det här rummet.", "You do not have permission to do that in this room.": "Du har inte behörighet att göra det i det här rummet.", - "Fetching third party location failed": "Det gick inte att hämta platsdata från tredje part", + "Fetching third party location failed": "Misslyckades att hämta platsdata från tredje part", "All notifications are currently disabled for all targets.": "Alla aviseringar är för tillfället avstängda för alla mål.", "Uploading report": "Laddar upp rapport", "Sunday": "söndag", @@ -363,7 +363,7 @@ "When I'm invited to a room": "När jag bjuds in till ett rum", "Can't update user notification settings": "Kan inte uppdatera användaraviseringsinställningarna", "Notify for all other messages/rooms": "Avisera för alla andra meddelanden/rum", - "Unable to look up room ID from server": "Det gick inte att hämta rums-ID:t från servern", + "Unable to look up room ID from server": "Kunde inte hämta rums-ID:t från servern", "Couldn't find a matching Matrix room": "Kunde inte hitta ett matchande Matrix-rum", "Invite to this room": "Bjud in till rummet", "Thursday": "torsdag", @@ -371,8 +371,8 @@ "Back": "Tillbaka", "Reply": "Svara", "Show message in desktop notification": "Visa meddelande i skrivbordsavisering", - "Unhide Preview": "Visa förhandsvisning", - "Unable to join network": "Det gick inte att ansluta till nätverket", + "Unhide Preview": "Visa förhandsgranskning", + "Unable to join network": "Kunde inte ansluta till nätverket", "Sorry, your browser is not able to run %(brand)s.": "Beklagar, din webbläsare kan inte köra %(brand)s.", "Messages in group chats": "Meddelanden i gruppchattar", "Yesterday": "igår", @@ -381,7 +381,7 @@ "Unable to fetch notification target list": "Kunde inte hämta aviseringsmållistan", "Set Password": "Välj lösenord", "Off": "Av", - "%(brand)s does not know how to join a room on this network": "%(brand)s kan inte gå med i ett rum på det här nätverket", + "%(brand)s does not know how to join a room on this network": "%(brand)s vet inte hur den ska gå med i ett rum på det här nätverket", "Mentions only": "Endast omnämnande", "Failed to remove tag %(tagName)s from room": "Det gick inte att radera etiketten %(tagName)s från rummet", "You can now return to your account after signing out, and sign in on other devices.": "Du kan nu återgå till ditt konto efter att ha loggat ut, och logga in på andra enheter.", @@ -532,7 +532,7 @@ "Sent messages will be stored until your connection has returned.": "Skickade meddelanden kommer att lagras tills anslutningen är tillbaka.", "There's no one else here! Would you like to invite others or stop warning about the empty room?": "Det är ingen annan här! Vill du bjuda in någon eller sluta varna om det tomma rummet?", "Room": "Rum", - "Clear filter": "Töm filter", + "Clear filter": "Rensa filter", "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Försökte ladda en viss punkt i det här rummets tidslinje, men du har inte behörighet att visa det aktuella meddelandet.", "Tried to load a specific point in this room's timeline, but was unable to find it.": "Försökte ladda en specifik punkt i det här rummets tidslinje, men kunde inte hitta den.", "Success": "Framgång", @@ -835,7 +835,7 @@ "An error ocurred whilst trying to remove the widget from the room": "Ett fel inträffade vid borttagning av widget från rummet", "Demote yourself?": "Degradera dig själv?", "Demote": "Degradera", - "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.": "När någon lägger en URL i sitt meddelande, kan URL-förhandsvisning ge mer information om länken, såsom titel, beskrivning, och en bild från webbplatsen.", + "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.": "När någon lägger en URL i sitt meddelande, kan URL-förhandsgranskning ge mer information om länken, såsom titel, beskrivning, och en bild från webbplatsen.", "You can't send any messages until you review and agree to our terms and conditions.": "Du kan inte skicka några meddelanden innan du granskar och godkänner våra villkor.", "System Alerts": "Systemvarningar", "Sorry, your homeserver is too old to participate in this room.": "Tyvärr, din hemserver är för gammal för att delta i det här rummet.", @@ -1358,10 +1358,10 @@ "Use an Integration Manager to manage bots, widgets, and sticker packs.": "Använd en integrationshanterare för att hantera bottar, widgets och dekalpaket.", "Manage integrations": "Hantera integrationer", "Integration Managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Integrationshanterare får konfigurationsdata och kan ändra widgetar, skicka rumsinbjudningar och ställa in behörighetsnivåer å dina vägnar.", - "Close preview": "Stäng förhandsvisning", + "Close preview": "Stäng förhandsgranskning", "Room %(name)s": "Rum %(name)s", "Recent rooms": "Senaste rummen", - "Loading room preview": "Laddar förhandsvisning av rummet", + "Loading room preview": "Laddar förhandsgranskning av rummet", "Re-join": "Gå med igen", "Try to join anyway": "Försök att gå med ändå", "Join the discussion": "Gå med i diskussionen", @@ -2218,5 +2218,12 @@ "%(brand)s failed to get the protocol list from the homeserver. The homeserver may be too old to support third party networks.": "%(brand)s misslyckades att hämta protokollistan från hemservern. Hemservern kan vara för gammal för att stödja tredjepartsnätverk.", "%(brand)s failed to get the public room list.": "%(brand)s misslyckades att hämta listan över offentliga rum.", "The homeserver may be unavailable or overloaded.": "Hemservern kan vara otillgänglig eller överbelastad.", - "Delete the room address %(alias)s and remove %(name)s from the directory?": "Radera rumsadressen %(alias)s och ta bort %(name)s från den här katalogen?" + "Delete the room address %(alias)s and remove %(name)s from the directory?": "Radera rumsadressen %(alias)s och ta bort %(name)s från den här katalogen?", + "delete the address.": "radera adressen.", + "View": "Visa", + "Find a room…": "Hitta ett rum…", + "Find a room… (e.g. %(exampleRoom)s)": "Hitta ett rum… (t.ex. %(exampleRoom)s)", + "If you can't find the room you're looking for, ask for an invite or Create a new room.": "Om du inte hittar rummet du letar efter, be om en inbjudan eller skapa ett nytt rum.", + "Explore rooms in %(communityName)s": "Utforska rum i %(communityName)s", + "Search rooms": "Sök bland rum" } From baa6d8a29430348ace9a5e67d19468d6c6a2d51b Mon Sep 17 00:00:00 2001 From: Jason Robinson Date: Fri, 4 Sep 2020 12:42:46 +0300 Subject: [PATCH 086/286] Correctly template in Jitsi widget auth if such exists Also add roomId to the widget data and URL template. It's needed by the Element Web Jitsi code to produce auth for the Jitsi backend. --- src/CallHandler.js | 4 ++-- src/components/views/elements/AppTile.js | 11 +++++++++-- src/utils/WidgetUtils.js | 11 ++++++++--- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/CallHandler.js b/src/CallHandler.js index 702613bb81..3c7e7fcc78 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -405,13 +405,12 @@ async function _startCallApp(roomId, type) { confId = `JitsiConference${generateHumanReadableId()}`; } - let widgetUrl = WidgetUtils.getLocalJitsiWrapperUrl(); + let widgetUrl = WidgetUtils.getLocalJitsiWrapperUrl({auth: jitsiAuth}); // TODO: Remove URL hacks when the mobile clients eventually support v2 widgets const parsedUrl = new URL(widgetUrl); parsedUrl.search = ''; // set to empty string to make the URL class use searchParams instead parsedUrl.searchParams.set('confId', confId); - parsedUrl.searchParams.set('auth', jitsiAuth); widgetUrl = parsedUrl.toString(); const widgetData = { @@ -419,6 +418,7 @@ async function _startCallApp(roomId, type) { isAudioOnly: type === 'voice', domain: jitsiDomain, auth: jitsiAuth, + roomId: roomId, }; const widgetId = ( diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 299025f949..9e0dd3c6c2 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -603,6 +603,7 @@ export default class AppTile extends React.Component { // TODO: Namespace themes through some standard 'theme': SettingsStore.getValue("theme"), }); + console.log("DEBUG TEMPLATEDURL APPTILE", vars); if (vars.conferenceId === undefined) { // we'll need to parse the conference ID out of the URL for v1 Jitsi widgets @@ -626,7 +627,10 @@ export default class AppTile extends React.Component { if (WidgetType.JITSI.matches(this.props.app.type)) { console.log("Replacing Jitsi widget URL with local wrapper"); - url = WidgetUtils.getLocalJitsiWrapperUrl({forLocalRender: true}); + url = WidgetUtils.getLocalJitsiWrapperUrl({ + forLocalRender: true, + auth: this.props.app.data ? this.props.app.data.auth : null, + }); url = this._addWurlParams(url); } else { url = this._getSafeUrl(this.state.widgetUrl); @@ -637,7 +641,10 @@ export default class AppTile extends React.Component { _getPopoutUrl() { if (WidgetType.JITSI.matches(this.props.app.type)) { return this._templatedUrl( - WidgetUtils.getLocalJitsiWrapperUrl({forLocalRender: false}), + WidgetUtils.getLocalJitsiWrapperUrl({ + forLocalRender: false, + auth: this.props.app.data ? this.props.app.data.auth : null, + }), this.props.app.type, ); } else { diff --git a/src/utils/WidgetUtils.js b/src/utils/WidgetUtils.js index 645953210d..c9666d90d5 100644 --- a/src/utils/WidgetUtils.js +++ b/src/utils/WidgetUtils.js @@ -448,16 +448,21 @@ export default class WidgetUtils { return encodeURIComponent(`${widgetLocation}::${widgetUrl}`); } - static getLocalJitsiWrapperUrl(opts: {forLocalRender?: boolean}={}) { + static getLocalJitsiWrapperUrl(opts: {forLocalRender?: boolean, auth?: string}={}) { // NB. we can't just encodeURIComponent all of these because the $ signs need to be there - const queryString = [ + const queryParts = [ 'conferenceDomain=$domain', 'conferenceId=$conferenceId', 'isAudioOnly=$isAudioOnly', 'displayName=$matrix_display_name', 'avatarUrl=$matrix_avatar_url', 'userId=$matrix_user_id', - ].join('&'); + 'roomId=$matrix_room_id', + ]; + if (opts.auth) { + queryParts.push(`auth=${opts.auth}`); + } + const queryString = queryParts.join('&'); let baseUrl = window.location; if (window.location.protocol !== "https:" && !opts.forLocalRender) { From 13dbfa6b8560ade438d488f98900e99f9438f847 Mon Sep 17 00:00:00 2001 From: Jason Robinson Date: Fri, 4 Sep 2020 12:44:43 +0300 Subject: [PATCH 087/286] Remove debug statement --- src/components/views/elements/AppTile.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 9e0dd3c6c2..dd1082e6c3 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -603,7 +603,6 @@ export default class AppTile extends React.Component { // TODO: Namespace themes through some standard 'theme': SettingsStore.getValue("theme"), }); - console.log("DEBUG TEMPLATEDURL APPTILE", vars); if (vars.conferenceId === undefined) { // we'll need to parse the conference ID out of the URL for v1 Jitsi widgets From 46bc6b1d149ed2c93914e5ee9bdfa3eb596df1e7 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 4 Sep 2020 11:37:06 +0100 Subject: [PATCH 088/286] Rename key backup to secure backup --- res/css/_components.scss | 2 +- ...ckupPanel.scss => _SecureBackupPanel.scss} | 2 +- ...KeyBackupPanel.js => SecureBackupPanel.js} | 2 +- .../tabs/user/SecurityUserSettingsTab.js | 10 +-- src/i18n/strings/en_EN.json | 62 +++++++++---------- 5 files changed, 39 insertions(+), 39 deletions(-) rename res/css/views/settings/{_KeyBackupPanel.scss => _SecureBackupPanel.scss} (95%) rename src/components/views/settings/{KeyBackupPanel.js => SecureBackupPanel.js} (99%) diff --git a/res/css/_components.scss b/res/css/_components.scss index 45ed6b3300..be1818cf8e 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -199,10 +199,10 @@ @import "./views/settings/_E2eAdvancedPanel.scss"; @import "./views/settings/_EmailAddresses.scss"; @import "./views/settings/_IntegrationManager.scss"; -@import "./views/settings/_KeyBackupPanel.scss"; @import "./views/settings/_Notifications.scss"; @import "./views/settings/_PhoneNumbers.scss"; @import "./views/settings/_ProfileSettings.scss"; +@import "./views/settings/_SecureBackupPanel.scss"; @import "./views/settings/_SetIdServer.scss"; @import "./views/settings/_SetIntegrationManager.scss"; @import "./views/settings/_UpdateCheckButton.scss"; diff --git a/res/css/views/settings/_KeyBackupPanel.scss b/res/css/views/settings/_SecureBackupPanel.scss similarity index 95% rename from res/css/views/settings/_KeyBackupPanel.scss rename to res/css/views/settings/_SecureBackupPanel.scss index 872162caad..3522a81e32 100644 --- a/res/css/views/settings/_KeyBackupPanel.scss +++ b/res/css/views/settings/_SecureBackupPanel.scss @@ -1,6 +1,6 @@ /* Copyright 2018 New Vector 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. diff --git a/src/components/views/settings/KeyBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js similarity index 99% rename from src/components/views/settings/KeyBackupPanel.js rename to src/components/views/settings/SecureBackupPanel.js index 8a74276f58..35009bc22d 100644 --- a/src/components/views/settings/KeyBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -23,7 +23,7 @@ import { _t } from '../../../languageHandler'; import Modal from '../../../Modal'; import { isSecureBackupRequired } from '../../../utils/WellKnownUtils'; -export default class KeyBackupPanel extends React.PureComponent { +export default class SecureBackupPanel extends React.PureComponent { constructor(props) { super(props); diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js index 90dcc0b658..c25ac438e0 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js @@ -29,6 +29,7 @@ import {sleep} from "../../../../../utils/promise"; import dis from "../../../../../dispatcher/dispatcher"; import {privateShouldBeEncrypted} from "../../../../../createRoom"; import {SettingLevel} from "../../../../../settings/SettingLevel"; +import SecureBackupPanel from "../../SecureBackupPanel"; export class IgnoredUser extends React.Component { static propTypes = { @@ -288,12 +289,11 @@ export default class SecurityUserSettingsTab extends React.Component { const SettingsFlag = sdk.getComponent('views.elements.SettingsFlag'); const EventIndexPanel = sdk.getComponent('views.settings.EventIndexPanel'); - const KeyBackupPanel = sdk.getComponent('views.settings.KeyBackupPanel'); - const keyBackup = ( + const secureBackup = (
- {_t("Key backup")} + {_t("Secure Backup")}
- +
); @@ -352,7 +352,7 @@ export default class SecurityUserSettingsTab extends React.Component {
{_t("Encryption")}
- {keyBackup} + {secureBackup} {eventIndex} {crossSigning} {this._renderCurrentDeviceInfo()} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 95b6c23a77..464edba6ce 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -697,36 +697,6 @@ "Connecting to integration manager...": "Connecting to integration manager...", "Cannot connect to integration manager": "Cannot connect to integration manager", "The integration manager is offline or it cannot reach your homeserver.": "The integration manager is offline or it cannot reach your homeserver.", - "Delete Backup": "Delete Backup", - "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.": "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.", - "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.", - "Unable to load key backup status": "Unable to load key backup status", - "Restore from Backup": "Restore from Backup", - "This session is backing up your keys. ": "This session is backing up your keys. ", - "This session is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.": "This session is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.", - "Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.": "Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.", - "Connect this session to Key Backup": "Connect this session to Key Backup", - "not stored": "not stored", - "Backing up %(sessionsRemaining)s keys...": "Backing up %(sessionsRemaining)s keys...", - "All keys backed up": "All keys backed up", - "Backup has a valid signature from this user": "Backup has a valid signature from this user", - "Backup has a invalid signature from this user": "Backup has a invalid signature from this user", - "Backup has a signature from unknown user with ID %(deviceId)s": "Backup has a signature from unknown user with ID %(deviceId)s", - "Backup has a signature from unknown session with ID %(deviceId)s": "Backup has a signature from unknown session with ID %(deviceId)s", - "Backup has a valid signature from this session": "Backup has a valid signature from this session", - "Backup has an invalid signature from this session": "Backup has an invalid signature from this session", - "Backup has a valid signature from verified session ": "Backup has a valid signature from verified session ", - "Backup has a valid signature from unverified session ": "Backup has a valid signature from unverified session ", - "Backup has an invalid signature from verified session ": "Backup has an invalid signature from verified session ", - "Backup has an invalid signature from unverified session ": "Backup has an invalid signature from unverified session ", - "Backup is not signed by any of your sessions": "Backup is not signed by any of your sessions", - "This backup is trusted because it has been restored on this session": "This backup is trusted because it has been restored on this session", - "Backup version: ": "Backup version: ", - "Algorithm: ": "Algorithm: ", - "Backup key stored: ": "Backup key stored: ", - "Your keys are not being backed up from this session.": "Your keys are not being backed up from this session.", - "Back up your keys before signing out to avoid losing them.": "Back up your keys before signing out to avoid losing them.", - "Start using Key Backup": "Start using Key Backup", "Error saving email notification preferences": "Error saving email notification preferences", "An error occurred whilst saving your email notification preferences.": "An error occurred whilst saving your email notification preferences.", "Keywords": "Keywords", @@ -758,6 +728,36 @@ "Display Name": "Display Name", "Profile picture": "Profile picture", "Save": "Save", + "Delete Backup": "Delete Backup", + "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.": "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.", + "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.", + "Unable to load key backup status": "Unable to load key backup status", + "Restore from Backup": "Restore from Backup", + "This session is backing up your keys. ": "This session is backing up your keys. ", + "This session is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.": "This session is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.", + "Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.": "Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.", + "Connect this session to Key Backup": "Connect this session to Key Backup", + "not stored": "not stored", + "Backing up %(sessionsRemaining)s keys...": "Backing up %(sessionsRemaining)s keys...", + "All keys backed up": "All keys backed up", + "Backup has a valid signature from this user": "Backup has a valid signature from this user", + "Backup has a invalid signature from this user": "Backup has a invalid signature from this user", + "Backup has a signature from unknown user with ID %(deviceId)s": "Backup has a signature from unknown user with ID %(deviceId)s", + "Backup has a signature from unknown session with ID %(deviceId)s": "Backup has a signature from unknown session with ID %(deviceId)s", + "Backup has a valid signature from this session": "Backup has a valid signature from this session", + "Backup has an invalid signature from this session": "Backup has an invalid signature from this session", + "Backup has a valid signature from verified session ": "Backup has a valid signature from verified session ", + "Backup has a valid signature from unverified session ": "Backup has a valid signature from unverified session ", + "Backup has an invalid signature from verified session ": "Backup has an invalid signature from verified session ", + "Backup has an invalid signature from unverified session ": "Backup has an invalid signature from unverified session ", + "Backup is not signed by any of your sessions": "Backup is not signed by any of your sessions", + "This backup is trusted because it has been restored on this session": "This backup is trusted because it has been restored on this session", + "Backup version: ": "Backup version: ", + "Algorithm: ": "Algorithm: ", + "Backup key stored: ": "Backup key stored: ", + "Your keys are not being backed up from this session.": "Your keys are not being backed up from this session.", + "Back up your keys before signing out to avoid losing them.": "Back up your keys before signing out to avoid losing them.", + "Start using Key Backup": "Start using Key Backup", "Identity Server URL must be HTTPS": "Identity Server URL must be HTTPS", "Not a valid Identity Server (status code %(code)s)": "Not a valid Identity Server (status code %(code)s)", "Could not connect to Identity Server": "Could not connect to Identity Server", @@ -904,7 +904,7 @@ "Bulk options": "Bulk options", "Accept all %(invitedRooms)s invites": "Accept all %(invitedRooms)s invites", "Reject all %(invitedRooms)s invites": "Reject all %(invitedRooms)s invites", - "Key backup": "Key backup", + "Secure Backup": "Secure Backup", "Message search": "Message search", "Cross-signing": "Cross-signing", "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.", From e3f47525e55be59888654ee4d80f097e73ec198b Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 4 Sep 2020 11:37:56 +0100 Subject: [PATCH 089/286] Rename backup CSS classes --- res/css/views/settings/_SecureBackupPanel.scss | 12 ++++++------ src/components/views/settings/SecureBackupPanel.js | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/res/css/views/settings/_SecureBackupPanel.scss b/res/css/views/settings/_SecureBackupPanel.scss index 3522a81e32..548e72fbc3 100644 --- a/res/css/views/settings/_SecureBackupPanel.scss +++ b/res/css/views/settings/_SecureBackupPanel.scss @@ -15,23 +15,23 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_KeyBackupPanel_sigValid, .mx_KeyBackupPanel_sigInvalid, -.mx_KeyBackupPanel_deviceVerified, .mx_KeyBackupPanel_deviceNotVerified { +.mx_SecureBackupPanel_sigValid, .mx_SecureBackupPanel_sigInvalid, +.mx_SecureBackupPanel_deviceVerified, .mx_SecureBackupPanel_deviceNotVerified { font-weight: bold; } -.mx_KeyBackupPanel_sigValid, .mx_KeyBackupPanel_deviceVerified { +.mx_SecureBackupPanel_sigValid, .mx_SecureBackupPanel_deviceVerified { color: $e2e-verified-color; } -.mx_KeyBackupPanel_sigInvalid, .mx_KeyBackupPanel_deviceNotVerified { +.mx_SecureBackupPanel_sigInvalid, .mx_SecureBackupPanel_deviceNotVerified { color: $e2e-warning-color; } -.mx_KeyBackupPanel_deviceName { +.mx_SecureBackupPanel_deviceName { font-style: italic; } -.mx_KeyBackupPanel_buttonRow { +.mx_SecureBackupPanel_buttonRow { margin: 1em 0; } diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js index 35009bc22d..684818aa1d 100644 --- a/src/components/views/settings/SecureBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -229,14 +229,14 @@ export default class SecureBackupPanel extends React.PureComponent { let backupSigStatuses = this.state.backupSigStatus.sigs.map((sig, i) => { const deviceName = sig.device ? (sig.device.getDisplayName() || sig.device.deviceId) : null; const validity = sub => - + {sub} ; const verify = sub => - + {sub} ; - const device = sub => {deviceName}; + const device = sub => {deviceName}; const fromThisDevice = ( sig.device && sig.device.getFingerprint() === MatrixClientPeg.get().getDeviceEd25519Key() @@ -324,7 +324,7 @@ export default class SecureBackupPanel extends React.PureComponent { } const buttonRow = ( -
+
{restoreButtonCaption}     @@ -355,7 +355,7 @@ export default class SecureBackupPanel extends React.PureComponent {

{encryptedMessageAreEncrypted}

{_t("Back up your keys before signing out to avoid losing them.")}

-
+
{_t("Start using Key Backup")} From 19e9e0137c027cbf1766b1fed4d8c7471be27d61 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 4 Sep 2020 13:05:30 +0100 Subject: [PATCH 090/286] Reorganise backup panel so feature description is always present This ensure the feature description is always shown at the top. --- .../views/settings/SecureBackupPanel.js | 77 ++++++++++--------- src/i18n/strings/en_EN.json | 3 +- 2 files changed, 43 insertions(+), 37 deletions(-) diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js index 684818aa1d..ec345f6317 100644 --- a/src/components/views/settings/SecureBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -165,31 +165,30 @@ export default class SecureBackupPanel extends React.PureComponent { render() { const Spinner = sdk.getComponent("elements.Spinner"); const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); - const encryptedMessageAreEncrypted = _t( - "Encrypted messages are secured with end-to-end encryption. " + - "Only you and the recipient(s) have the keys to read these messages.", + const featureDescription = _t( + "Back up your encryption keys with your account data in case you " + + "lose access to your sessions. Your keys will be secured with a " + + "unique Recovery Key.", ); + let statusDescription; + let details; + let actions; if (this.state.error) { - return ( + statusDescription = (
{_t("Unable to load key backup status")}
); } else if (this.state.loading) { - return ; + statusDescription = ; } else if (this.state.backupInfo) { - let clientBackupStatus; let restoreButtonCaption = _t("Restore from Backup"); if (MatrixClientPeg.get().getKeyBackupEnabled()) { - clientBackupStatus =
-

{encryptedMessageAreEncrypted}

-

✅ {_t("This session is backing up your keys. ")}

-
; + statusDescription =

✅ {_t("This session is backing up your keys. ")}

; } else { - clientBackupStatus =
-

{encryptedMessageAreEncrypted}

+ statusDescription = <>

{_t( "This session is not backing up your keys, " + "but you do have an existing backup you can restore from " + @@ -200,7 +199,7 @@ export default class SecureBackupPanel extends React.PureComponent { "Connect this session to key backup before signing out to avoid " + "losing any keys that may only be on this session.", )}

-
; + ; restoreButtonCaption = _t("Connect this session to Key Backup"); } @@ -323,17 +322,7 @@ export default class SecureBackupPanel extends React.PureComponent { ; } - const buttonRow = ( -
- - {restoreButtonCaption} -     - {deleteBackupButton} -
- ); - - return
-
{clientBackupStatus}
+ details = (
{_t("Advanced")}
{_t("Backup version: ")}{this.state.backupInfo.version}
@@ -343,24 +332,40 @@ export default class SecureBackupPanel extends React.PureComponent {
{backupSigStatuses}
{trustedLocally}
- {buttonRow} -
; - } else { - return
-
-

{_t( - "Your keys are not being backed up from this session.", {}, - {b: sub => {sub}}, - )}

-

{encryptedMessageAreEncrypted}

-

{_t("Back up your keys before signing out to avoid losing them.")}

+ ); + + actions = ( +
+ + {restoreButtonCaption} +     + {deleteBackupButton}
+ ); + } else { + statusDescription = <> +

{_t( + "Your keys are not being backed up from this session.", {}, + {b: sub => {sub}}, + )}

+

{_t("Back up your keys before signing out to avoid losing them.")}

+ ; + actions = (
{_t("Start using Key Backup")}
-
; + ); } + + return ( +
+

{featureDescription}

+ {statusDescription} + {details} + {actions} +
+ ); } } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 464edba6ce..843373ad07 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -730,7 +730,7 @@ "Save": "Save", "Delete Backup": "Delete Backup", "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.": "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.", - "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.", + "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.": "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.", "Unable to load key backup status": "Unable to load key backup status", "Restore from Backup": "Restore from Backup", "This session is backing up your keys. ": "This session is backing up your keys. ", @@ -1732,6 +1732,7 @@ "Clear cache and resync": "Clear cache and resync", "%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!", "Updating %(brand)s": "Updating %(brand)s", + "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.", "I don't want my encrypted messages": "I don't want my encrypted messages", "Manually export keys": "Manually export keys", "You'll lose access to your encrypted messages": "You'll lose access to your encrypted messages", From cc2c179a0f429fe2b5890bd9ffcf76f78c67521d Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 4 Sep 2020 13:18:57 +0100 Subject: [PATCH 091/286] Set default text colour for Settings tabs --- res/css/views/settings/tabs/_SettingsTab.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/res/css/views/settings/tabs/_SettingsTab.scss b/res/css/views/settings/tabs/_SettingsTab.scss index 5f00ed86f7..892f5fe744 100644 --- a/res/css/views/settings/tabs/_SettingsTab.scss +++ b/res/css/views/settings/tabs/_SettingsTab.scss @@ -14,6 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ +.mx_SettingsTab { + color: $muted-fg-color; +} + .mx_SettingsTab_warningText { color: $warning-color; } From b484bc5e098e72b09d8904042f4cf67540c304db Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 4 Sep 2020 13:52:14 +0100 Subject: [PATCH 092/286] Rearrange backup status to always have advanced --- .../views/settings/SecureBackupPanel.js | 68 ++++++++++--------- src/i18n/strings/en_EN.json | 6 +- 2 files changed, 38 insertions(+), 36 deletions(-) diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js index ec345f6317..93e28c8bc9 100644 --- a/src/components/views/settings/SecureBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -165,24 +165,28 @@ export default class SecureBackupPanel extends React.PureComponent { render() { const Spinner = sdk.getComponent("elements.Spinner"); const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); - const featureDescription = _t( - "Back up your encryption keys with your account data in case you " + - "lose access to your sessions. Your keys will be secured with a " + - "unique Recovery Key.", - ); + + const { + loading, + error, + backupInfo, + backupSigStatus, + backupKeyStored, + sessionsRemaining, + } = this.state; let statusDescription; - let details; + let extraDetails; let actions; - if (this.state.error) { + if (error) { statusDescription = (
{_t("Unable to load key backup status")}
); - } else if (this.state.loading) { + } else if (loading) { statusDescription = ; - } else if (this.state.backupInfo) { + } else if (backupInfo) { let restoreButtonCaption = _t("Restore from Backup"); if (MatrixClientPeg.get().getKeyBackupEnabled()) { @@ -203,15 +207,7 @@ export default class SecureBackupPanel extends React.PureComponent { restoreButtonCaption = _t("Connect this session to Key Backup"); } - let keyStatus; - if (this.state.backupKeyStored === true) { - keyStatus = _t("in secret storage"); - } else { - keyStatus = _t("not stored"); - } - let uploadStatus; - const { sessionsRemaining } = this.state; if (!MatrixClientPeg.get().getKeyBackupEnabled()) { // No upload status to show when backup disabled. uploadStatus = ""; @@ -225,7 +221,7 @@ export default class SecureBackupPanel extends React.PureComponent {
; } - let backupSigStatuses = this.state.backupSigStatus.sigs.map((sig, i) => { + let backupSigStatuses = backupSigStatus.sigs.map((sig, i) => { const deviceName = sig.device ? (sig.device.getDisplayName() || sig.device.deviceId) : null; const validity = sub => @@ -306,12 +302,12 @@ export default class SecureBackupPanel extends React.PureComponent { {sigStatus}
; }); - if (this.state.backupSigStatus.sigs.length === 0) { + if (backupSigStatus.sigs.length === 0) { backupSigStatuses = _t("Backup is not signed by any of your sessions"); } let trustedLocally; - if (this.state.backupSigStatus.trusted_locally) { + if (backupSigStatus.trusted_locally) { trustedLocally = _t("This backup is trusted because it has been restored on this session"); } @@ -322,17 +318,13 @@ export default class SecureBackupPanel extends React.PureComponent { ; } - details = ( -
- {_t("Advanced")} -
{_t("Backup version: ")}{this.state.backupInfo.version}
-
{_t("Algorithm: ")}{this.state.backupInfo.algorithm}
-
{_t("Backup key stored: ")}{keyStatus}
- {uploadStatus} -
{backupSigStatuses}
-
{trustedLocally}
-
- ); + extraDetails = <> +
{_t("Backup version: ")}{backupInfo.version}
+
{_t("Algorithm: ")}{backupInfo.algorithm}
+ {uploadStatus} +
{backupSigStatuses}
+
{trustedLocally}
+ ; actions = (
@@ -361,9 +353,19 @@ export default class SecureBackupPanel extends React.PureComponent { return (
-

{featureDescription}

+

{_t( + "Back up your encryption keys with your account data in case you " + + "lose access to your sessions. Your keys will be secured with a " + + "unique Recovery Key.", + )}

{statusDescription} - {details} +
+ {_t("Advanced")} +
{_t("Backup key stored: ")}{ + backupKeyStored === true ? _t("in secret storage") : _t("not stored") + }
+ {extraDetails} +
{actions}
); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 843373ad07..49e8b1d2e8 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -730,14 +730,12 @@ "Save": "Save", "Delete Backup": "Delete Backup", "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.": "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.", - "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.": "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.", "Unable to load key backup status": "Unable to load key backup status", "Restore from Backup": "Restore from Backup", "This session is backing up your keys. ": "This session is backing up your keys. ", "This session is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.": "This session is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.", "Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.": "Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.", "Connect this session to Key Backup": "Connect this session to Key Backup", - "not stored": "not stored", "Backing up %(sessionsRemaining)s keys...": "Backing up %(sessionsRemaining)s keys...", "All keys backed up": "All keys backed up", "Backup has a valid signature from this user": "Backup has a valid signature from this user", @@ -754,10 +752,12 @@ "This backup is trusted because it has been restored on this session": "This backup is trusted because it has been restored on this session", "Backup version: ": "Backup version: ", "Algorithm: ": "Algorithm: ", - "Backup key stored: ": "Backup key stored: ", "Your keys are not being backed up from this session.": "Your keys are not being backed up from this session.", "Back up your keys before signing out to avoid losing them.": "Back up your keys before signing out to avoid losing them.", "Start using Key Backup": "Start using Key Backup", + "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.": "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.", + "Backup key stored: ": "Backup key stored: ", + "not stored": "not stored", "Identity Server URL must be HTTPS": "Identity Server URL must be HTTPS", "Not a valid Identity Server (status code %(code)s)": "Not a valid Identity Server (status code %(code)s)", "Could not connect to Identity Server": "Could not connect to Identity Server", From 217f14591021c2718408aac1935132edeae3611e Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 4 Sep 2020 13:55:25 +0100 Subject: [PATCH 093/286] Switch to imports in backup panel --- src/components/views/settings/SecureBackupPanel.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js index 93e28c8bc9..683120f5b6 100644 --- a/src/components/views/settings/SecureBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -17,11 +17,14 @@ limitations under the License. import React from 'react'; -import * as sdk from '../../../index'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import { _t } from '../../../languageHandler'; import Modal from '../../../Modal'; import { isSecureBackupRequired } from '../../../utils/WellKnownUtils'; +import Spinner from '../elements/Spinner'; +import AccessibleButton from '../elements/AccessibleButton'; +import QuestionDialog from '../dialogs/QuestionDialog'; +import RestoreKeyBackupDialog from '../dialogs/keybackup/RestoreKeyBackupDialog'; export default class SecureBackupPanel extends React.PureComponent { constructor(props) { @@ -135,7 +138,6 @@ export default class SecureBackupPanel extends React.PureComponent { } _deleteBackup = () => { - const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog'); Modal.createTrackedDialog('Delete Backup', '', QuestionDialog, { title: _t('Delete Backup'), description: _t( @@ -155,7 +157,6 @@ export default class SecureBackupPanel extends React.PureComponent { } _restoreBackup = async () => { - const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog'); Modal.createTrackedDialog( 'Restore Backup', '', RestoreKeyBackupDialog, null, null, /* priority = */ false, /* static = */ true, @@ -163,9 +164,6 @@ export default class SecureBackupPanel extends React.PureComponent { } render() { - const Spinner = sdk.getComponent("elements.Spinner"); - const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); - const { loading, error, From b7ef80cd503f3d5349dc6c3ee274672336206f52 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Fri, 4 Sep 2020 18:03:00 +0000 Subject: [PATCH 094/286] Translated using Weblate (Swedish) Currently translated at 94.2% (2218 of 2355 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 75 ++++++++++++++++++++++++++++------------ 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index eb416bb2d0..7351171228 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -33,7 +33,7 @@ "Ban": "Banna", "Attachment": "Bilaga", "Call Timeout": "Samtalstimeout", - "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.": "Det går inte att ansluta till en hemserver via HTTP då adressen i webbläsaren är HTTPS. Använd HTTPS, eller aktivera osäkra skript.", + "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.": "Kan inte ansluta till en hemserver via HTTP då adressen i webbläsaren är HTTPS. Använd HTTPS, eller aktivera osäkra skript.", "Change Password": "Byt lösenord", "%(senderName)s changed their profile picture.": "%(senderName)s bytte sin profilbild.", "%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s bytte rummets namn till %(roomName)s.", @@ -42,9 +42,9 @@ "Changes your display nickname": "Ändrar ditt visningsnamn", "Click here to fix": "Klicka här för att fixa", "Click to mute audio": "Klicka för att tysta ljud", - "Click to mute video": "Klicka för att stänga av video", + "Click to mute video": "Klicka för att tysta videon", "click to reveal": "klicka för att avslöja", - "Click to unmute video": "Klicka för att sätta på video", + "Click to unmute video": "Klicka för att sluta tysta videon", "Click to unmute audio": "Klicka för att sätta på ljud", "Command error": "Kommandofel", "Commands": "Kommandon", @@ -78,11 +78,11 @@ "Failed to join room": "Misslyckades att gå med i rummet", "Failed to kick": "Misslyckades att kicka", "Failed to leave room": "Det gick inte att lämna rummet", - "Failed to load timeline position": "Det gick inte att hämta positionen på tidslinjen", + "Failed to load timeline position": "Misslyckades att hämta positionen på tidslinjen", "Failed to mute user": "Misslyckades att tysta användaren", - "Failed to reject invite": "Det gick inte att avböja inbjudan", + "Failed to reject invite": "Misslyckades att avböja inbjudan", "Failed to reject invitation": "Misslyckades att avböja inbjudan", - "Failed to send email": "Det gick inte att skicka epost", + "Failed to send email": "Misslyckades att skicka e-post", "Failed to send request.": "Misslyckades att sända begäran.", "Failed to set display name": "Misslyckades att ange visningsnamn", "Failed to unban": "Misslyckades att avbanna", @@ -100,7 +100,7 @@ "Decline": "Avvisa", "Drop File Here": "Dra filen hit", "Enter passphrase": "Ange lösenfras", - "Error: Problem communicating with the given homeserver.": "Fel: Det gick inte att kommunicera med den angivna hemservern.", + "Error: Problem communicating with the given homeserver.": "Fel: Problem med att kommunicera med den angivna hemservern.", "Failed to fetch avatar URL": "Det gick inte att hämta avatar-URL", "Failed to upload profile picture!": "Misslyckades att ladda upp profilbild!", "Failure to create room": "Misslyckades att skapa rummet", @@ -116,7 +116,7 @@ "Home": "Hem", "Homeserver is": "Hemservern är", "Identity Server is": "Identitetsservern är", - "I have verified my email address": "Jag har verifierat min epostadress", + "I have verified my email address": "Jag har verifierat min e-postadress", "Import": "Importera", "Import E2E room keys": "Importera rumskrypteringsnycklar", "Incoming call from %(name)s": "Inkommande samtal från %(name)s", @@ -155,7 +155,7 @@ "Mute": "Tysta", "Name": "Namn", "New passwords don't match": "De nya lösenorden matchar inte", - "New passwords must match each other.": "De nya lösenorden måste vara de samma.", + "New passwords must match each other.": "De nya lösenorden måste matcha.", "not specified": "inte specificerad", "Notifications": "Aviseringar", "(not supported by this browser)": "(stöds inte av webbläsaren)", @@ -187,7 +187,7 @@ "Remove": "Ta bort", "%(senderName)s requested a VoIP conference.": "%(senderName)s begärde ett VoIP-gruppsamtal.", "Results from DuckDuckGo": "Resultat från DuckDuckGo", - "Return to login screen": "Tillbaka till login-skärmen", + "Return to login screen": "Tillbaka till inloggningsskärmen", "%(brand)s does not have permission to send you notifications - please check your browser settings": "%(brand)s har inte tillstånd att skicka aviseringar - kontrollera webbläsarens inställningar", "%(brand)s was not given permission to send notifications - please try again": "%(brand)s fick inte tillstånd att skicka aviseringar - försök igen", "%(brand)s version:": "%(brand)s-version:", @@ -205,7 +205,7 @@ "%(senderDisplayName)s sent an image.": "%(senderDisplayName)s skickade en bild.", "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s bjöd in %(targetDisplayName)s att gå med i rummet.", "Server error": "Serverfel", - "Server may be unavailable, overloaded, or search timed out :(": "Servern kan vara otillgänglig, överbelastad, eller så tog sökningen för lång tid :(", + "Server may be unavailable, overloaded, or search timed out :(": "Servern kan vara otillgänglig eller överbelastad, eller så tog sökningen för lång tid :(", "Server may be unavailable, overloaded, or you hit a bug.": "Servern kan vara otillgänglig eller överbelastad, eller så stötte du på en bugg.", "Server unavailable, overloaded, or something else went wrong.": "Servern är otillgänglig eller överbelastad, eller så gick något annat fel.", "Session ID": "Sessions-ID", @@ -239,10 +239,10 @@ "PM": "EM", "Submit": "Skicka in", "The maximum permitted number of widgets have already been added to this room.": "Den största tillåtna mängden widgetar har redan tillagts till rummet.", - "The phone number entered looks invalid": "Det angivna telefonnumret är ogiltigt", + "The phone number entered looks invalid": "Det angivna telefonnumret ser ogiltigt ut", "This email address is already in use": "Den här e-postadressen används redan", "This email address was not found": "Den här e-postadressen finns inte", - "The email address linked to your account must be entered.": "Epostadressen som är kopplad till ditt konto måste anges.", + "The email address linked to your account must be entered.": "E-postadressen som är kopplad till ditt konto måste anges.", "Online": "Online", "Unnamed room": "Namnlöst rum", "World readable": "Alla kan läsa", @@ -487,9 +487,9 @@ "Set a display name:": "Ange ett visningsnamn:", "Upload an avatar:": "Ladda upp en avatar:", "This server does not support authentication with a phone number.": "Denna server har inte support för autentisering via telefonnummer.", - "Uploading %(filename)s and %(count)s others|other": "Laddar upp %(filename)s och %(count)s andra", + "Uploading %(filename)s and %(count)s others|other": "Laddar upp %(filename)s och %(count)s till", "Uploading %(filename)s and %(count)s others|zero": "Laddar upp %(filename)s", - "Uploading %(filename)s and %(count)s others|one": "Laddar upp %(filename)s och %(count)s annan", + "Uploading %(filename)s and %(count)s others|one": "Laddar upp %(filename)s och %(count)s till", "This doesn't appear to be a valid email address": "Det här verkar inte vara en giltig e-postadress", "Verification Pending": "Avvaktar verifiering", "Unable to add email address": "Kunde inte lägga till e-postadress", @@ -526,8 +526,8 @@ "You seem to be in a call, are you sure you want to quit?": "Du verkar vara i ett samtal, är du säker på att du vill avsluta?", "Active call": "Aktivt samtal", "%(count)s of your messages have not been sent.|one": "Ditt meddelande skickades inte.", - "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|other": "Skicka om alla eller ångra alla nu. Du kan även välja enskilda meddelanden för att skicka om eller ångra.", - "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|one": "Skicka om meddelande eller ångra meddelande nu.", + "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|other": "Skicka om alla eller avbryt alla nu. Du kan även välja enskilda meddelanden för att skicka om eller avbryta.", + "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|one": "Skicka om meddelande eller avbryt meddelande nu.", "Connectivity to the server has been lost.": "Anslutning till servern har brutits.", "Sent messages will be stored until your connection has returned.": "Skickade meddelanden kommer att lagras tills anslutningen är tillbaka.", "There's no one else here! Would you like to invite others or stop warning about the empty room?": "Det är ingen annan här! Vill du bjuda in någon eller sluta varna om det tomma rummet?", @@ -538,7 +538,7 @@ "Success": "Framgång", "Unable to remove contact information": "Kunde inte ta bort kontaktuppgifter", "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "Ett e-brev har skickats till %(emailAddress)s. När du har öppnat länken i det, klicka nedan.", - "Please note you are logging into the %(hs)s server, not matrix.org.": "Observera att du loggar in på %(hs)s-servern, inte matrix.org.", + "Please note you are logging into the %(hs)s server, not matrix.org.": "Observera att du loggar in på servern %(hs)s, inte matrix.org.", "This homeserver doesn't offer any login flows which are supported by this client.": "Denna hemserver erbjuder inga inloggningsflöden som stöds av den här klienten.", "Upload new:": "Ladda upp ny:", "Copied!": "Kopierat!", @@ -843,10 +843,10 @@ "Please contact your service administrator to continue using the service.": "Kontakta din tjänstadministratör för att fortsätta använda tjänsten.", "This homeserver has hit its Monthly Active User limit.": "Hemservern har nått sin månatliga gräns för användaraktivitet.", "This homeserver has exceeded one of its resource limits.": "Hemservern har överskridit en av sina resursgränser.", - "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please contact your service administrator to continue using the service.": "Ditt meddelande skickades inte för hemservern har nått sin månatliga gräns för användaraktivitet. Kontakta din serviceadministratör för att fortsätta använda servicen.", - "Your message wasn't sent because this homeserver has exceeded a resource limit. Please contact your service administrator to continue using the service.": "Ditt meddelande skickades inte för hemservern har överskridit en av sina resursgränser. Kontakta din serviceadministratör för att fortsätta använda servicen.", + "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please contact your service administrator to continue using the service.": "Ditt meddelande skickades inte eftersom hemservern har nått sin månatliga gräns för användaraktivitet. Vänligen kontakta din serviceadministratör för att fortsätta använda tjänsten.", + "Your message wasn't sent because this homeserver has exceeded a resource limit. Please contact your service administrator to continue using the service.": "Ditt meddelande skickades inte eftersom hemservern har överskridit en av sina resursgränser. Vänligen kontakta din serviceadministratör för att fortsätta använda tjänsten.", "Legal": "Juridiskt", - "Please contact your service administrator to continue using this service.": "Kontakta din serviceadministratör för att fortsätta använda servicen.", + "Please contact your service administrator to continue using this service.": "Vänligen kontakta din tjänstadministratör för att fortsätta använda tjänsten.", "This room has been replaced and is no longer active.": "Detta rum har ersatts och är inte längre aktivt.", "The conversation continues here.": "Konversationen fortsätter här.", "Only room administrators will see this warning": "Endast rumsadministratörer kommer att se denna varning", @@ -1069,7 +1069,7 @@ "Your password has been reset.": "Ditt lösenord har återställts.", "Set a new password": "Ange ett nytt lösenord", "General failure": "Allmänt fel", - "This homeserver does not support login using email address.": "Denna hemserver stöder inte inloggning med epostadress.", + "This homeserver does not support login using email address.": "Denna hemserver stöder inte inloggning med e-postadress.", "Create account": "Skapa konto", "Registration has been disabled on this homeserver.": "Registrering har inaktiverats på denna hemserver.", "Unable to query for supported registration methods.": "Det gick inte att fråga efter stödda registreringsmetoder.", @@ -2225,5 +2225,34 @@ "Find a room… (e.g. %(exampleRoom)s)": "Hitta ett rum… (t.ex. %(exampleRoom)s)", "If you can't find the room you're looking for, ask for an invite or Create a new room.": "Om du inte hittar rummet du letar efter, be om en inbjudan eller skapa ett nytt rum.", "Explore rooms in %(communityName)s": "Utforska rum i %(communityName)s", - "Search rooms": "Sök bland rum" + "Search rooms": "Sök bland rum", + "You have %(count)s unread notifications in a prior version of this room.|other": "Du har %(count)s olästa aviseringar i en tidigare version av det här rummet.", + "You have %(count)s unread notifications in a prior version of this room.|one": "Du har %(count)s oläst avisering i en tidigare version av det här rummet.", + "Create community": "Skapa gemenskap", + "Failed to find the general chat for this community": "Misslyckades att hitta den allmänna chatten för den här gemenskapen", + "Notification settings": "Aviseringsinställningar", + "All settings": "Alla inställningar", + "Feedback": "Återkoppling", + "Community settings": "Gemenskapsinställningar", + "User settings": "Användarinställningar", + "Switch to light mode": "Byt till ljust läge", + "Switch to dark mode": "Byt till mörkt läge", + "Switch theme": "Byt tema", + "User menu": "Användarmeny", + "Community and user menu": "Gemenskaps- och användarmeny", + "Verify this login": "Verifiera den här inloggningen", + "Session verified": "Session verifierad", + "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.": "Om du ändrar lösenordet så kommer alla nycklar för totalsträckskryptering att återställas på alla dina sessioner, vilket gör krypterad chatthistorik oläslig. Ställ in nyckelsäkerhetskopiering eller exportera dina rumsnycklar från en annan session innan du återställer lösenordet.", + "Your Matrix account on ": "Ditt Matrix-konto på ", + "No identity server is configured: add one in server settings to reset your password.": "Ingen identitetsserver konfigurerad: lägg till en i serverinställningarna för att återställa ditt konto.", + "A verification email will be sent to your inbox to confirm setting your new password.": "Ett e-brev för verifiering skickas till din inkorg för att bekräfta ditt nya lösenord.", + "Invalid homeserver discovery response": "Ogiltigt hemserverupptäcktssvar", + "Failed to get autodiscovery configuration from server": "Misslyckades att få konfiguration för autoupptäckt från servern", + "Invalid base_url for m.homeserver": "Ogiltig base_url för m.homeserver", + "Homeserver URL does not appear to be a valid Matrix homeserver": "Hemserver-URL:en verkar inte vara en giltig Matrix-hemserver", + "Invalid identity server discovery response": "Ogiltigt identitetsserverupptäcktssvar", + "Invalid base_url for m.identity_server": "Ogiltig base_url för m.identity_server", + "Identity server URL does not appear to be a valid identity server": "Identitetsserver-URL:en verkar inte vara en giltig Matrix-identitetsserver", + "This account has been deactivated.": "Det här kontot har avaktiverats.", + "Failed to perform homeserver discovery": "Misslyckades att genomföra hemserverupptäckt" } From d25cb36babed4f775db213907302fa1817299463 Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Sat, 5 Sep 2020 11:58:32 +0000 Subject: [PATCH 095/286] Translated using Weblate (Swedish) Currently translated at 94.2% (2218 of 2355 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 7351171228..8bd05474b4 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -93,7 +93,7 @@ "Active call (%(roomName)s)": "Aktiv samtal (%(roomName)s)", "Add": "Lägg till", "Admin Tools": "Admin-verktyg", - "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.": "Det gick inte att ansluta till hemservern - kontrollera anslutningen, se till att hemserverns SSL-certifikat är betrott, och att inget webbläsartillägg blockerar förfrågningar.", + "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.": "Kan inte ansluta till homeservern - kolla din nätverksanslutning, se till att homeserverns SSL certifikat är betrodd, och att ingen webbläsare extension blockerar begäran.", "%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s ändrade behörighetsnivå för %(powerLevelDiffText)s.", "Close": "Stäng", "Custom": "Egen", From 355d8f584361fef3e5a605a94cda584cc2d03d68 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 5 Sep 2020 15:06:31 -0600 Subject: [PATCH 096/286] Update openid_credentials Widget API action for MSC1960 updates We now need to send a `state` and `original_request_id` per MSC1960's recent adjustments --- src/WidgetMessaging.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/WidgetMessaging.js b/src/WidgetMessaging.js index 6aed08c39d..56ee96ab21 100644 --- a/src/WidgetMessaging.js +++ b/src/WidgetMessaging.js @@ -186,7 +186,14 @@ export default class WidgetMessaging { isUserWidget: this.isUserWidget, onFinished: async (confirm) => { - const responseBody = {success: confirm}; + const responseBody = { + // Legacy (early draft) fields + success: confirm, + + // New style MSC1961 fields + state: confirm ? "allowed" : "blocked", + original_request_id: ev.requestId, // eslint-disable-line camelcase + }; if (confirm) { const credentials = await MatrixClientPeg.get().getOpenIdToken(); Object.assign(responseBody, credentials); From db1eb895525f5c6c30d42cfba8c1c10a09cf4f9e Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 5 Sep 2020 15:10:28 -0600 Subject: [PATCH 097/286] MSC numbers are hard to remember --- src/WidgetMessaging.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WidgetMessaging.js b/src/WidgetMessaging.js index 56ee96ab21..c68e926ac1 100644 --- a/src/WidgetMessaging.js +++ b/src/WidgetMessaging.js @@ -190,7 +190,7 @@ export default class WidgetMessaging { // Legacy (early draft) fields success: confirm, - // New style MSC1961 fields + // New style MSC1960 fields state: confirm ? "allowed" : "blocked", original_request_id: ev.requestId, // eslint-disable-line camelcase }; From c5927af11f0d8ca0e4d3420a7e867e6b2d2eb846 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Sat, 5 Sep 2020 01:48:58 +0000 Subject: [PATCH 098/286] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2355 of 2355 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-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 4ec70aa8f7..3348e0cb2c 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2460,5 +2460,6 @@ "Failed to find the general chat for this community": "找不到此社群的一般聊天紀錄", "Community settings": "社群設定", "User settings": "使用者設定", - "Community and user menu": "社群與使用者選單" + "Community and user menu": "社群與使用者選單", + "Privacy": "隱私" } From 54e08fd0209515c99e8d5f2fff4c8ab4c8e26d06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Fri, 4 Sep 2020 17:45:37 +0000 Subject: [PATCH 099/286] Translated using Weblate (Estonian) Currently translated at 100.0% (2355 of 2355 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/et/ --- src/i18n/strings/et.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index c617470601..ed9c138931 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -2457,5 +2457,6 @@ "Failed to find the general chat for this community": "Ei õnnestunud tuvastada selle kogukonna üldist rühmavestlust", "Community settings": "Kogukonna seadistused", "User settings": "Kasutaja seadistused", - "Community and user menu": "Kogukonna ja kasutaja menüü" + "Community and user menu": "Kogukonna ja kasutaja menüü", + "Privacy": "Privaatsus" } From ea241bbb5e2f48fad8416a3b31d5f03756117f65 Mon Sep 17 00:00:00 2001 From: XoseM Date: Sat, 5 Sep 2020 16:51:25 +0000 Subject: [PATCH 100/286] Translated using Weblate (Galician) Currently translated at 100.0% (2355 of 2355 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/gl/ --- src/i18n/strings/gl.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index 0348203887..b8dcc48b68 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -2449,5 +2449,14 @@ "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Poderías desactivalo se a sala vai ser utilizada para colaborar con equipos externos que teñen o seu propio servidor. Esto non se pode cambiar máis tarde.", "Create a room in %(communityName)s": "Crear unha sala en %(communityName)s", "Block anyone not part of %(serverName)s from ever joining this room.": "Evitar que calquera externo a %(serverName)s se poida unir a esta sala.", - "Create community": "Crear comunidade" + "Create community": "Crear comunidade", + "Privacy": "Privacidade", + "There was an error updating your community. The server is unable to process your request.": "Algo fallou ó actualizar a comunidade. O servidor non é quen de procesar a solicitude.", + "Update community": "Actualizar comunidade", + "May include members not in %(communityName)s": "Podería incluir membros que non están en %(communityName)s", + "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Iniciar unha conversa con alguén utilizando o seu nome, nome de usuaria (como ) ou enderezo de email. Esto non as convidará a %(communityName)s. Para convidar a alguén a %(communityName)s, preme aquí.", + "Failed to find the general chat for this community": "Non se atopou o chat xenérico para esta comunidade", + "Community settings": "Axustes da comunidade", + "User settings": "Axustes de usuaria", + "Community and user menu": "Menú de usuaria e comunidade" } From 865280cc35e3e57d2951c9eddf938f7dd68ccf69 Mon Sep 17 00:00:00 2001 From: Nikita Epifanov Date: Sat, 5 Sep 2020 07:58:28 +0000 Subject: [PATCH 101/286] Translated using Weblate (Russian) Currently translated at 100.0% (2355 of 2355 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index 919077af24..311cab0a97 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -2453,5 +2453,6 @@ "Failed to find the general chat for this community": "Не удалось найти общий чат для этого сообщества", "Community settings": "Настройки сообщества", "User settings": "Пользовательские настройки", - "Community and user menu": "Сообщество и меню пользователя" + "Community and user menu": "Сообщество и меню пользователя", + "Privacy": "Конфиденциальность" } From 40032deb37912058e0545ae1ce5977b621cd8c68 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Sat, 5 Sep 2020 12:00:15 +0000 Subject: [PATCH 102/286] Translated using Weblate (Swedish) Currently translated at 100.0% (2355 of 2355 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 169 +++++++++++++++++++++++++++++++++++---- 1 file changed, 153 insertions(+), 16 deletions(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 8bd05474b4..6cbbfd9098 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -93,7 +93,7 @@ "Active call (%(roomName)s)": "Aktiv samtal (%(roomName)s)", "Add": "Lägg till", "Admin Tools": "Admin-verktyg", - "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.": "Kan inte ansluta till homeservern - kolla din nätverksanslutning, se till att homeserverns SSL certifikat är betrodd, och att ingen webbläsare extension blockerar begäran.", + "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.": "Kan inte ansluta till hemservern - vänligen kolla din nätverksanslutning, se till att hemserverns SSL-certifikat är betrott, och att inget webbläsartillägg blockerar förfrågningar.", "%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s ändrade behörighetsnivå för %(powerLevelDiffText)s.", "Close": "Stäng", "Custom": "Egen", @@ -101,7 +101,7 @@ "Drop File Here": "Dra filen hit", "Enter passphrase": "Ange lösenfras", "Error: Problem communicating with the given homeserver.": "Fel: Problem med att kommunicera med den angivna hemservern.", - "Failed to fetch avatar URL": "Det gick inte att hämta avatar-URL", + "Failed to fetch avatar URL": "Misslyckades att hämta avatar-URL", "Failed to upload profile picture!": "Misslyckades att ladda upp profilbild!", "Failure to create room": "Misslyckades att skapa rummet", "Favourites": "Favoriter", @@ -289,7 +289,7 @@ "Sunday": "söndag", "Messages sent by bot": "Meddelanden från bottar", "Notification targets": "Aviseringsmål", - "Failed to set direct chat tag": "Det gick inte att markera rummet som direkt-chatt", + "Failed to set direct chat tag": "Misslyckades att markera rummet som direktchatt", "Today": "idag", "You are not receiving desktop notifications": "Du får inte skrivbordsaviseringar", "Friday": "fredag", @@ -320,7 +320,7 @@ "An error occurred whilst saving your email notification preferences.": "Ett fel inträffade då e-postaviseringsinställningarna sparades.", "Explore Room State": "Utforska rumsstatus", "Source URL": "Käll-URL", - "Failed to add tag %(tagName)s to room": "Det gick inte att lägga till etiketten \"%(tagName)s\" till rummet", + "Failed to add tag %(tagName)s to room": "Misslyckades att lägga till etiketten %(tagName)s till rummet", "Filter results": "Filtrera resultaten", "Members": "Medlemmar", "No update available.": "Ingen uppdatering tillgänglig.", @@ -383,7 +383,7 @@ "Off": "Av", "%(brand)s does not know how to join a room on this network": "%(brand)s vet inte hur den ska gå med i ett rum på det här nätverket", "Mentions only": "Endast omnämnande", - "Failed to remove tag %(tagName)s from room": "Det gick inte att radera etiketten %(tagName)s från rummet", + "Failed to remove tag %(tagName)s from room": "Misslyckades att radera etiketten %(tagName)s från rummet", "You can now return to your account after signing out, and sign in on other devices.": "Du kan nu återgå till ditt konto efter att ha loggat ut, och logga in på andra enheter.", "Enable email notifications": "Aktivera e-postaviseringar", "Download this file": "Ladda ner filen", @@ -486,7 +486,7 @@ "Code": "Kod", "Set a display name:": "Ange ett visningsnamn:", "Upload an avatar:": "Ladda upp en avatar:", - "This server does not support authentication with a phone number.": "Denna server har inte support för autentisering via telefonnummer.", + "This server does not support authentication with a phone number.": "Denna server stöder inte autentisering via telefonnummer.", "Uploading %(filename)s and %(count)s others|other": "Laddar upp %(filename)s och %(count)s till", "Uploading %(filename)s and %(count)s others|zero": "Laddar upp %(filename)s", "Uploading %(filename)s and %(count)s others|one": "Laddar upp %(filename)s och %(count)s till", @@ -793,8 +793,8 @@ "Learn more about how we use analytics.": "Läs mer om hur vi använder statistik.", "Analytics": "Statistik", "Send analytics data": "Skicka statistik", - "Passphrases must match": "Passfraser måste matcha", - "Passphrase must not be empty": "Lösenfras får inte vara tom", + "Passphrases must match": "Lösenfraser måste matcha", + "Passphrase must not be empty": "Lösenfrasen får inte vara tom", "Confirm passphrase": "Bekräfta lösenfrasen", "%(senderName)s changed the pinned messages for the room.": "%(senderName)s ändrade fastnålade meddelanden för rummet.", "Message Pinning": "Fastnålning av meddelanden", @@ -802,9 +802,9 @@ "No pinned messages.": "Inga fastnålade meddelanden.", "Pinned Messages": "Fastnålade meddelanden", "Pin Message": "Nåla fast meddelande", - "The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.": "Den exporterade filen kommer att låta någon som kan läsa den att dekryptera alla krypterade meddelanden som du kan se, så du bör vara noga med att hålla den säker. För att hjälpa till med detta, bör du ange en lösenfras nedan, som kommer att användas för att kryptera exporterad data. Det kommer bara vara möjligt att importera data genom att använda samma lösenfras.", - "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.": "Denna process möjliggör import av krypteringsnycklar som tidigare exporterats från en annan Matrix-klient. Du kommer då kunna dekryptera alla meddelanden som den andra klienten kunde dekryptera.", - "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "Den exporterade filen kommer vara skyddad med en lösenfras. Du måste ange lösenfrasen här, för att dekryptera filen.", + "The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.": "Den exporterade filen kommer att låta de som kan läsa den att dekryptera alla krypterade meddelanden som du kan se, så du bör vara noga med att hålla den säker. För att hjälpa till med detta, bör du ange en lösenfras nedan, som kommer att användas för att kryptera exporterad data. Det kommer bara vara möjligt att importera data genom att använda samma lösenfras.", + "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.": "Denna process möjliggör import av krypteringsnycklar som tidigare exporterats från en annan Matrix-klient. Du kommer då kunna avkryptera alla meddelanden som den andra klienten kunde avkryptera.", + "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "Den exporterade filen kommer vara skyddad med en lösenfras. Du måste ange lösenfrasen här, för att avkryptera filen.", "Flair": "Emblem", "Showing flair for these communities:": "Visar emblem för dessa gemenskaper:", "This room is not showing flair for any communities": "Detta rum visar inte emblem för några gemenskaper", @@ -1072,7 +1072,7 @@ "This homeserver does not support login using email address.": "Denna hemserver stöder inte inloggning med e-postadress.", "Create account": "Skapa konto", "Registration has been disabled on this homeserver.": "Registrering har inaktiverats på denna hemserver.", - "Unable to query for supported registration methods.": "Det gick inte att fråga efter stödda registreringsmetoder.", + "Unable to query for supported registration methods.": "Kunde inte fråga efter stödda registreringsmetoder.", "Create your account": "Skapa ditt konto", "Retry": "Försök igen", "Don't ask again": "Fråga inte igen", @@ -1504,7 +1504,7 @@ "Navigation": "Navigering", "Calls": "Samtal", "Room List": "Rumslista", - "Autocomplete": "Komplettera automatiskt", + "Autocomplete": "Autokomplettera", "Alt": "Alt", "Alt Gr": "Alt Gr", "Shift": "Shift", @@ -2145,7 +2145,7 @@ "Invalid Recovery Key": "Ogiltig återställningsnyckel", "Security Phrase": "Säkerhetsfras", "Unable to access secret storage. Please verify that you entered the correct recovery passphrase.": "Kunde inte komma åt hemlig lagring. Vänligen verifiera att du matade in rätt återställningslösenfras.", - "Enter your Security Phrase or to continue.": "Mata in din säkerhetsfras eller för att fortsätta.", + "Enter your Security Phrase or to continue.": "Ange din säkerhetsfras eller för att fortsätta.", "Security Key": "Säkerhetsnyckel", "Use your Security Key to continue.": "Använd din säkerhetsnyckel för att fortsätta.", "Restoring keys from backup": "Återställer nycklar från säkerhetskopia", @@ -2160,7 +2160,7 @@ "Keys restored": "Nycklar återställda", "Failed to decrypt %(failedCount)s sessions!": "Misslyckades att avkryptera %(failedCount)s sessioner!", "Successfully restored %(sessionCount)s keys": "Återställde framgångsrikt %(sessionCount)s nycklar", - "Enter recovery passphrase": "Mata in återställningslösenfras", + "Enter recovery passphrase": "Ange återställningslösenfras", "Warning: you should only set up key backup from a trusted computer.": "Varning: Du bör endast sätta upp nyckelsäkerhetskopiering från en betrodd dator.", "Access your secure message history and set up secure messaging by entering your recovery passphrase.": "Kom åt din säkra meddelandehistorik och sätt upp säker kommunikation genom att skriva in din återställningslösenfras.", "If you've forgotten your recovery passphrase you can use your recovery key or set up new recovery options": "Om du har glömt din återställningslösenfras kan du använda din återställningsnyckel eller ställa in nya återställningsalternativ", @@ -2254,5 +2254,142 @@ "Invalid base_url for m.identity_server": "Ogiltig base_url för m.identity_server", "Identity server URL does not appear to be a valid identity server": "Identitetsserver-URL:en verkar inte vara en giltig Matrix-identitetsserver", "This account has been deactivated.": "Det här kontot har avaktiverats.", - "Failed to perform homeserver discovery": "Misslyckades att genomföra hemserverupptäckt" + "Failed to perform homeserver discovery": "Misslyckades att genomföra hemserverupptäckt", + "Privacy": "Sekretess", + "There was an error updating your community. The server is unable to process your request.": "Ett fel inträffade vid uppdatering av din gemenskap. Serven kan inte behandla din begäran.", + "Update community": "Uppdatera gemenskap", + "May include members not in %(communityName)s": "Kan inkludera medlemmar som inte är i %(communityName)s", + "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Starta en konversation med någon med deras namn, användarnamn (som ) eller e-postadress. Detta kommer inte att bjuda in dem till %(communityName)s. För att bjuda in någon till %(communityName)s, klicka här.", + "Syncing...": "Synkar…", + "Signing In...": "Loggar in…", + "If you've joined lots of rooms, this might take a while": "Om du har gått med i många rum kan det här ta ett tag", + "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).": "Ditt nya konto (%(newAccountId)s) är registrerat, men du är redan inloggad på ett annat konto (%(loggedInUserId)s).", + "Continue with previous account": "Fortsätt med de tidigare kontot", + "Log in to your new account.": "Logga in i ditt nya konto.", + "You can now close this window or log in to your new account.": "Du kan nu stänga det här fönstret eller logga in i ditt nya konto.", + "Registration Successful": "Registrering lyckades", + "Use Recovery Key or Passphrase": "Använd återställningsnyckel eller -lösenfras", + "Use Recovery Key": "Använd återställningsnyckel", + "Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.": "Bekräfta din identitet genom att verifiera den här inloggningen från en av dina andra sessioner och ge den åtkomst till krypterade meddelanden.", + "This requires the latest %(brand)s on your other devices:": "Det här kräver den senaste %(brand)s på dina andra enheter:", + "%(brand)s Web": "%(brand)s webb", + "%(brand)s Desktop": "%(brand)s skrivbord", + "%(brand)s iOS": "%(brand)s iOS", + "%(brand)s Android": "%(brand)s Android", + "or another cross-signing capable Matrix client": "eller en annan Matrix-klient som stöder korssignering", + "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Din nya session har nu verifierats. Den har tillgång till dina krypterade meddelanden, och andra användare kommer att se den som betrodd.", + "Your new session is now verified. Other users will see it as trusted.": "Din nya session har nu verifierats. Andra användare kommer att se den som betrodd.", + "Without completing security on this session, it won’t have access to encrypted messages.": "Utan att slutföra säkerheten på den här sessionen får den inte tillgång till krypterade meddelanden.", + "Failed to re-authenticate due to a homeserver problem": "Misslyckades att återautentisera p.g.a. ett hemserverproblem", + "Failed to re-authenticate": "Misslyckades att återautentisera", + "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.": "Återfå tillgång till ditt konto och återställ krypteringsnycklar som lagrats i den här sessionen. Utan dem kan du inte läsa alla dina säkra meddelanden i någon session.", + "Enter your password to sign in and regain access to your account.": "Ange ditt lösenord för att logga in och återfå tillgång till ditt konto.", + "Forgotten your password?": "Glömt ditt lösenord?", + "Sign in and regain access to your account.": "Logga in och återfå tillgång till ditt konto.", + "You cannot sign in to your account. Please contact your homeserver admin for more information.": "Du kan inte logga in på ditt konto. Vänligen kontakta din hemserveradministratör för mer information.", + "You're signed out": "Du är utloggad", + "Clear personal data": "Rensa personlig information", + "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.": "Varning: Din personliga information (inklusive krypteringsnycklar) lagras fortfarande i den här sessionen. Rensa den om du är färdig med den här sessionen, eller vill logga in på ett annat konto.", + "Command Autocomplete": "Autokomplettering av kommandon", + "DuckDuckGo Results": "DuckDuckGo-resultat", + "Emoji Autocomplete": "Autokomplettering av emoji", + "Notification Autocomplete": "Autokomplettering av aviseringar", + "Room Autocomplete": "Autokomplettering av rum", + "User Autocomplete": "Autokomplettering av användare", + "Confirm encryption setup": "Bekräfta krypteringsinställning", + "Click the button below to confirm setting up encryption.": "Klicka på knappen nedan för att bekräfta inställning av kryptering.", + "Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.": "Skydda mot att förlora åtkomst till krypterade meddelanden och data genom att säkerhetskopiera krypteringsnycklar på din server.", + "Generate a Security Key": "Generera en säkerhetsnyckel", + "We’ll generate a Security Key for you to store somewhere safe, like a password manager or a safe.": "Vi kommer att generera en säkerhetsnyckel som du kan lagra någonstans säkert, som en lösenordshanterare eller ett kassaskåp.", + "Enter a Security Phrase": "Ange en säkerhetsfras", + "Use a secret phrase only you know, and optionally save a Security Key to use for backup.": "Använd en hemlig fras endast du känner till, och spara valfritt en säkerhetsnyckel att använda för säkerhetskopiering.", + "Enter your account password to confirm the upgrade:": "Ange ditt kontolösenord för att bekräfta uppgraderingen:", + "Restore your key backup to upgrade your encryption": "Återställ din nyckelsäkerhetskopia för att uppgradera din kryptering", + "Restore": "Återställ", + "You'll need to authenticate with the server to confirm the upgrade.": "Du kommer behöva autentisera mot servern för att bekräfta uppgraderingen.", + "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Uppgradera den här sessionen för att låta den verifiera andra sessioner, för att ge dem åtkomst till krypterade meddelanden och markera dem som betrodda för andra användare.", + "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.": "Ange en säkerhetsfras endast du känner till, eftersom den används för att hålla din data säker. För att vara säker bör inte återanvända ditt kontolösenord.", + "Enter a recovery passphrase": "Ange en återställningslösenfras", + "Great! This recovery passphrase looks strong enough.": "Strålande! Den här återställningslösenfrasen ser stark nog ut.", + "That matches!": "Det matchar!", + "Use a different passphrase?": "Använd en annan lösenfras?", + "That doesn't match.": "Det matchar inte.", + "Go back to set it again.": "Gå tillbaka och sätt den igen.", + "Enter your recovery passphrase a second time to confirm it.": "Ange din återställningslösenfras en gång till för att bekräfta den.", + "Confirm your recovery passphrase": "Bekräfta din återställningslösenfras", + "Store your Security Key somewhere safe, like a password manager or a safe, as it’s used to safeguard your encrypted data.": "Förvara din säkerhetsnyckel någonstans säkert, som en lösenordshanterare eller ett kassaskåp, eftersom den används för att skydda dina krypterade data.", + "Download": "Ladda ner", + "Unable to query secret storage status": "Kunde inte fråga efter status på hemlig lagring", + "If you cancel now, you may lose encrypted messages & data if you lose access to your logins.": "Om du avbryter nu så kan du förlora krypterade meddelanden och data om du förlorar åtkomst till dina inloggningar.", + "You can also set up Secure Backup & manage your keys in Settings.": "Du kan även ställa in säker säkerhetskopiering och hantera dina nycklar i inställningarna.", + "Set up Secure Backup": "Ställ in säker säkerhetskopiering", + "Upgrade your encryption": "Uppgradera din kryptering", + "Set a Security Phrase": "Sätt en säkerhetsfras", + "Confirm Security Phrase": "Bekräfta säkerhetsfras", + "Save your Security Key": "Spara din säkerhetsnyckel", + "Unable to set up secret storage": "Kunde inte sätta upp hemlig lagring", + "We'll store an encrypted copy of your keys on our server. Secure your backup with a recovery passphrase.": "Vi lagrar en krypterad kopia av dina nycklar på vår server. Säkra din säkerhetskopia med en återställningslösenfras.", + "For maximum security, this should be different from your account password.": "För maximal säkerhet bör det här skilja sig från ditt kontolösenord.", + "Set up with a recovery key": "Sätt en återställningsnyckel", + "Please enter your recovery passphrase a second time to confirm.": "Vänligen ange din återställningslösenfras en gång till för att bekräfta.", + "Repeat your recovery passphrase...": "Repetera din återställningslösenfras…", + "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.": "Din återställningsnyckel är ett skyddsnät - du kan använda den för att återfå åtkomst till dina krypterade meddelanden om du glömmer din återställningslösenfras.", + "Keep a copy of it somewhere secure, like a password manager or even a safe.": "Förvara en kopia av den någonstans säkert, som en lösenordshanterare eller till och med ett kassaskåp.", + "Your recovery key": "Din återställningsnyckel", + "Your recovery key has been copied to your clipboard, paste it to:": "Din återställningsnyckel har kopierats till ditt klippbord, klistra in den i:", + "Your recovery key is in your Downloads folder.": "Din återställningsnyckel är i din Hämtningar-mapp.", + "Print it and store it somewhere safe": "Skriv ut den och förvara den någonstans säkert", + "Save it on a USB key or backup drive": "Spara den på ett USB-minne eller en säkerhetskopieringshårddisk", + "Copy it to your personal cloud storage": "Kopiera den till din personliga molnlagring", + "Your keys are being backed up (the first backup could take a few minutes).": "Dina nycklar säkerhetskopieras (den första säkerhetskopieringen kan ta några minuter).", + "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.": "Om du inte ställer in säker meddelandeåterställning kommer du inte kunna återställa din krypterade meddelandehistorik om du loggar ut eller använder en annan session.", + "Set up Secure Message Recovery": "Ställ in säker meddelandeåterställning", + "Secure your backup with a recovery passphrase": "Säkra din säkerhetskopia med en återställningslösenfras", + "Make a copy of your recovery key": "Ta en kopia av din återställningsnyckel", + "Starting backup...": "Startar säkerhetskopiering…", + "Success!": "Framgång!", + "Create key backup": "Skapa nyckelsäkerhetskopia", + "Unable to create key backup": "Kunde inte skapa nyckelsäkerhetskopia", + "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.": "Om du inte ställer in säker meddelandeåterställning så kommer du förlora åtkomst till din säkra meddelandehistorik när du loggar ut.", + "If you don't want to set this up now, you can later in Settings.": "Om du inte vill ställa in det här nu så kan du göra det senare i inställningarna.", + "New Recovery Method": "Ny återställningsmetod", + "A new recovery passphrase and key for Secure Messages have been detected.": "En ny återställningslösenfras och -nyckel för säkra meddelanden har hittats.", + "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.": "Om du inte har ställt in den nya återställningsmetoden kan en angripare försöka komma åt ditt konto. Ändra ditt kontolösenord och ställ in en ny återställningsmetod omedelbart i inställningarna.", + "This session is encrypting history using the new recovery method.": "Den här sessionen krypterar historik med den nya återställningsmetoden.", + "Go to Settings": "Gå till inställningarna", + "Set up Secure Messages": "Ställ in säkra meddelanden", + "Recovery Method Removed": "Återställningsmetod borttagen", + "This session has detected that your recovery passphrase and key for Secure Messages have been removed.": "Den här sessionen har detekterat att din återställningslösenfras och -nyckel för säkra meddelanden har tagits bort.", + "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.": "Om du gjorde det av misstag kan du ställa in säkra meddelanden på den här sessionen som krypterar sessionens meddelandehistorik igen med en ny återställningsmetod.", + "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.": "Om du inte tog bort återställningsmetoden kan en angripare försöka komma åt ditt konto. Ändra ditt kontolösenord och ställ in en ny återställningsmetod omedelbart i inställningarna.", + "If disabled, messages from encrypted rooms won't appear in search results.": "Om den är inaktiverad visas inte meddelanden från krypterade rum i sökresultaten.", + "Disable": "Inaktivera", + "Not currently indexing messages for any room.": "Indexerar för närvarande inte meddelanden för något rum.", + "Currently indexing: %(currentRoom)s": "Indexerar för närvarande: %(currentRoom)s", + "%(brand)s is securely caching encrypted messages locally for them to appear in search results:": "%(brand)s cachar säkert krypterade meddelanden lokalt för att de ska visas i sökresultat:", + "Message downloading sleep time(ms)": "Vilotid för meddelandenedladdning (ms)", + "Navigate recent messages to edit": "Navigera nyliga meddelanden att redigera", + "Jump to start/end of the composer": "Hoppa till början/slut av meddelanderedigeraren", + "Navigate composer history": "Navigera redigerarhistorik", + "Cancel replying to a message": "Avbryt svar på ett meddelande", + "Toggle microphone mute": "Växla mikrofontystning", + "Toggle video on/off": "Växla video på/av", + "Scroll up/down in the timeline": "Skrolla upp/ner i tidslinjen", + "Dismiss read marker and jump to bottom": "Avfärda läsmarkering och hoppa till botten", + "Jump to oldest unread message": "Hoppa till äldsta olästa meddelandet", + "Upload a file": "Ladda upp en fil", + "Navigate up/down in the room list": "Navigera upp/ner i rumslistan", + "Select room from the room list": "Välj rum från rumslistan", + "Collapse room list section": "Kollapsa rumslistsektionen", + "Expand room list section": "Expandera rumslistsektionen", + "Clear room list filter field": "Rensa filterfältet för rumslistan", + "Previous/next unread room or DM": "Förra/nästa olästa rum eller DM", + "Previous/next room or DM": "Förra/nästa rum eller DM", + "Toggle the top left menu": "Växla menyn högst upp till vänster", + "Close dialog or context menu": "Stäng dialogrutan eller snabbmenyn", + "Activate selected button": "Aktivera den valda knappen", + "Toggle right panel": "Växla högerpanelen", + "Toggle this dialog": "Växla den här dialogrutan", + "Move autocomplete selection up/down": "Flytta autokompletteringssektionen upp/ner", + "Cancel autocomplete": "Stäng autokomplettering" } From ae83222d52749f473481fbb6e6807aad2e86d501 Mon Sep 17 00:00:00 2001 From: Jason Robinson Date: Mon, 7 Sep 2020 14:32:00 +0300 Subject: [PATCH 103/286] Add GetOpenIDCredentials constant to WidgetApi --- src/widgets/WidgetApi.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/widgets/WidgetApi.ts b/src/widgets/WidgetApi.ts index 15603e9437..74351720e2 100644 --- a/src/widgets/WidgetApi.ts +++ b/src/widgets/WidgetApi.ts @@ -34,6 +34,7 @@ export enum KnownWidgetActions { GetCapabilities = "capabilities", SendEvent = "send_event", UpdateVisibility = "visibility", + GetOpenIDCredentials = "get_openid", ReceiveOpenIDCredentials = "openid_credentials", SetAlwaysOnScreen = "set_always_on_screen", ClientReady = "im.vector.ready", From 4491d5c47892a4e4971c7a7d3539874488bd00cb Mon Sep 17 00:00:00 2001 From: "@a2sc:matrix.org" Date: Mon, 7 Sep 2020 11:29:17 +0000 Subject: [PATCH 104/286] Translated using Weblate (German) Currently translated at 100.0% (2355 of 2355 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 744715bc13..ea65273733 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -2447,5 +2447,14 @@ "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Private Räume können nur auf Einladung gefunden und betreten werden. Öffentliche Räume können von jedem/r in dieser Community gefunden und betreten werden.", "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "Du solltest dies aktivieren, wenn der Raum nur für die Zusammenarbeit mit internen Teams auf deinem Heimserver verwendet wird. Dies kann später nicht mehr geändert werden.", "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Du solltest dies deaktivieren, wenn der Raum für die Zusammenarbeit mit externen Teams auf deren Home-Server verwendet wird. Dies kann später nicht mehr geändert werden.", - "Block anyone not part of %(serverName)s from ever joining this room.": "Blockiere alle, die nicht Teil von %(serverName)s sind, diesen Raum jemals zu betreten." + "Block anyone not part of %(serverName)s from ever joining this room.": "Blockiere alle, die nicht Teil von %(serverName)s sind, diesen Raum jemals zu betreten.", + "Privacy": "Privatsphäre", + "There was an error updating your community. The server is unable to process your request.": "Beim Aktualisieren deiner Community ist ein Fehler aufgetreten. Der Server kann deine Anfrage nicht verarbeiten.", + "Update community": "Community aktualisieren", + "May include members not in %(communityName)s": "Kann Mitglieder enthalten, die nicht in %(communityName)s enthalten sind", + "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Starte ein Gespräch mit jemandem unter Verwendung seines/ihres Namens, Nutzernamens (wie ) oder E-Mail-Adresse. Dadurch werden sie nicht zu %(communityName)s eingeladen. Klicke hier hier, um jemanden zu %(communityName)s einzuladen.", + "Failed to find the general chat for this community": "Der allgemeine Chat für diese Community konnte nicht gefunden werden", + "Community settings": "Community-Einstellungen", + "User settings": "Nutzer-Einstellungen", + "Community and user menu": "Community- und Nutzer-Menü" } From a7d41281546e15eaed0ddc15cabd3277b411184a Mon Sep 17 00:00:00 2001 From: Johnny998 <78mikey87@gmail.com> Date: Mon, 7 Sep 2020 08:18:05 +0000 Subject: [PATCH 105/286] Translated using Weblate (Slovak) Currently translated at 69.8% (1644 of 2355 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sk/ --- src/i18n/strings/sk.json | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sk.json b/src/i18n/strings/sk.json index 667768fd57..5ed562e400 100644 --- a/src/i18n/strings/sk.json +++ b/src/i18n/strings/sk.json @@ -1798,5 +1798,18 @@ "Security & privacy": "Bezpečnosť & súkromie", "All settings": "Všetky nastavenia", "Feedback": "Spätná väzba", - "Indexed rooms:": "Indexované miestnosti:" + "Indexed rooms:": "Indexované miestnosti:", + "Unexpected server error trying to leave the room": "Neočakávaná chyba servera pri pokuse opustiť miestnosť", + "Emoji picker": "Vybrať emoji", + "Send a reply…": "Odoslať odpoveď…", + "Send a message…": "Odoslať správu…", + "Bold": "Tučné", + "Italics": "Kurzíva", + "Strikethrough": "Preškrtnuté", + "Leave Room": "Opustiť miestnosť", + "Direct message": "Priama správa", + "Security": "Zabezpečenie", + "Send a Direct Message": "Poslať priamu správu", + "User menu": "Používateľské menu", + "Toggle Italics": "Prepnúť kurzíva" } From d2e9ea58fde083564cfa5529ec988f6f89768b8e Mon Sep 17 00:00:00 2001 From: Jason Robinson Date: Mon, 7 Sep 2020 19:22:40 +0300 Subject: [PATCH 106/286] Add links to prosody openidtoken-jwt auth docs --- src/CallHandler.js | 4 +--- src/widgets/Jitsi.ts | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/CallHandler.js b/src/CallHandler.js index 3c7e7fcc78..1a1c71b55f 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -395,10 +395,8 @@ async function _startCallApp(roomId, type) { if (jitsiAuth === 'openidtoken-jwt') { // Create conference ID from room ID // For compatibility with Jitsi, use base32 without padding. - // If the room ID needs to be decoded from the conference ID, - // the receiver should first uppercase it if needed and then add padding. // More details here: - // TODO add link + // https://github.com/matrix-org/prosody-mod-auth-matrix-user-verification confId = base32.stringify(Buffer.from(roomId), { pad: false }); } else { // Create a random human readable conference ID diff --git a/src/widgets/Jitsi.ts b/src/widgets/Jitsi.ts index 1805913ad6..ca8de4468a 100644 --- a/src/widgets/Jitsi.ts +++ b/src/widgets/Jitsi.ts @@ -39,7 +39,7 @@ export class Jitsi { * * If the file does not exist, we assume no auth. * - * See TODO add link + * See https://github.com/matrix-org/prosody-mod-auth-matrix-user-verification */ public async getJitsiAuth(): Promise { if (!this.preferredDomain) { From 4b43e39d2aa0bf151384ed910b4a7df6c92bc4cb Mon Sep 17 00:00:00 2001 From: Jason Robinson Date: Tue, 8 Sep 2020 11:31:40 +0300 Subject: [PATCH 107/286] Code review related changes * drop room ID from jitsi widget data * reame queryParts variable --- src/CallHandler.js | 1 - src/utils/WidgetUtils.js | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/CallHandler.js b/src/CallHandler.js index 1a1c71b55f..27e8e34e16 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -416,7 +416,6 @@ async function _startCallApp(roomId, type) { isAudioOnly: type === 'voice', domain: jitsiDomain, auth: jitsiAuth, - roomId: roomId, }; const widgetId = ( diff --git a/src/utils/WidgetUtils.js b/src/utils/WidgetUtils.js index c9666d90d5..d5f6981476 100644 --- a/src/utils/WidgetUtils.js +++ b/src/utils/WidgetUtils.js @@ -450,7 +450,7 @@ export default class WidgetUtils { static getLocalJitsiWrapperUrl(opts: {forLocalRender?: boolean, auth?: string}={}) { // NB. we can't just encodeURIComponent all of these because the $ signs need to be there - const queryParts = [ + const queryStringParts = [ 'conferenceDomain=$domain', 'conferenceId=$conferenceId', 'isAudioOnly=$isAudioOnly', @@ -460,9 +460,9 @@ export default class WidgetUtils { 'roomId=$matrix_room_id', ]; if (opts.auth) { - queryParts.push(`auth=${opts.auth}`); + queryStringParts.push(`auth=${opts.auth}`); } - const queryString = queryParts.join('&'); + const queryString = queryStringParts.join('&'); let baseUrl = window.location; if (window.location.protocol !== "https:" && !opts.forLocalRender) { From a934878fdb70fd7d35c811dc27dbcd8f181f6336 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Tue, 8 Sep 2020 03:21:26 +0000 Subject: [PATCH 108/286] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2356 of 2356 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-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 3348e0cb2c..6aa980cd72 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2461,5 +2461,6 @@ "Community settings": "社群設定", "User settings": "使用者設定", "Community and user menu": "社群與使用者選單", - "Privacy": "隱私" + "Privacy": "隱私", + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "把 ( ͡° ͜ʖ ͡°) 加在純文字訊息前" } From b2292241f1330beb470e66e6ae83010cc72220ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Mon, 7 Sep 2020 14:43:07 +0000 Subject: [PATCH 109/286] Translated using Weblate (Estonian) Currently translated at 100.0% (2356 of 2356 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/et/ --- src/i18n/strings/et.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index ed9c138931..5500a4bd02 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -2458,5 +2458,6 @@ "Community settings": "Kogukonna seadistused", "User settings": "Kasutaja seadistused", "Community and user menu": "Kogukonna ja kasutaja menüü", - "Privacy": "Privaatsus" + "Privacy": "Privaatsus", + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Lisa ( ͡° ͜ʖ ͡°) smaili vormindamata sõnumi algusesse" } From 72901ad88cd01f9b366b5e49f6984b0a19c6cd2c Mon Sep 17 00:00:00 2001 From: "@a2sc:matrix.org" Date: Mon, 7 Sep 2020 20:50:40 +0000 Subject: [PATCH 110/286] Translated using Weblate (German) Currently translated at 100.0% (2356 of 2356 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-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 ea65273733..14a87f8308 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -2456,5 +2456,6 @@ "Failed to find the general chat for this community": "Der allgemeine Chat für diese Community konnte nicht gefunden werden", "Community settings": "Community-Einstellungen", "User settings": "Nutzer-Einstellungen", - "Community and user menu": "Community- und Nutzer-Menü" + "Community and user menu": "Community- und Nutzer-Menü", + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Stellt ( ͡° ͜ʖ ͡°) einer Klartextnachricht voran" } From e891e1ff05c6eaa513b0df866108a7305413015b Mon Sep 17 00:00:00 2001 From: Nikita Epifanov Date: Mon, 7 Sep 2020 13:33:24 +0000 Subject: [PATCH 111/286] Translated using Weblate (Russian) Currently translated at 100.0% (2356 of 2356 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index 311cab0a97..203f407ece 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -2454,5 +2454,6 @@ "Community settings": "Настройки сообщества", "User settings": "Пользовательские настройки", "Community and user menu": "Сообщество и меню пользователя", - "Privacy": "Конфиденциальность" + "Privacy": "Конфиденциальность", + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Добавляет ( ͡° ͜ʖ ͡°) к текстовому сообщению" } From 8e760318714b7877ae71a0178c70424df5e89b17 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Tue, 8 Sep 2020 07:02:33 +0000 Subject: [PATCH 112/286] Translated using Weblate (Swedish) Currently translated at 100.0% (2356 of 2356 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 6cbbfd9098..03a6bc1e2e 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -2391,5 +2391,6 @@ "Toggle right panel": "Växla högerpanelen", "Toggle this dialog": "Växla den här dialogrutan", "Move autocomplete selection up/down": "Flytta autokompletteringssektionen upp/ner", - "Cancel autocomplete": "Stäng autokomplettering" + "Cancel autocomplete": "Stäng autokomplettering", + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Lägger till ( ͡° ͜ʖ ͡°) i början på ett textmeddelande" } From d0c716d61c07666deb5bc5455705120e20f4f547 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 10:26:37 +0100 Subject: [PATCH 113/286] Fix permalink local linkification to not strip via servers --- src/utils/permalinks/Permalinks.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/utils/permalinks/Permalinks.js b/src/utils/permalinks/Permalinks.js index 466d1ed57d..d97ba55c4d 100644 --- a/src/utils/permalinks/Permalinks.js +++ b/src/utils/permalinks/Permalinks.js @@ -332,6 +332,10 @@ export function tryTransformPermalinkToLocalHref(permalink: string): string { if (permalinkParts.roomIdOrAlias) { const eventIdPart = permalinkParts.eventId ? `/${permalinkParts.eventId}` : ''; permalink = `#/room/${permalinkParts.roomIdOrAlias}${eventIdPart}`; + if (permalinkParts.viaServers.length > 0) { + const riotPermalinkConstructor = new SpecPermalinkConstructor(); + permalink += riotPermalinkConstructor.encodeServerCandidates(permalinkParts.viaServers); + } } else if (permalinkParts.groupId) { permalink = `#/group/${permalinkParts.groupId}`; } else if (permalinkParts.userId) { From 6a304ce16ef079eebb403056fbdd02a6fa89907e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 7 Sep 2020 17:31:32 +0100 Subject: [PATCH 114/286] Remove ancient RoomView props.eventPixelOffset --- src/components/structures/LoggedInView.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 33e7c4a238..1ac15caa4c 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -76,7 +76,6 @@ interface IProps { hideToSRUsers: boolean; resizeNotifier: ResizeNotifier; middleDisabled: boolean; - initialEventPixelOffset: number; leftDisabled: boolean; rightDisabled: boolean; // eslint-disable-next-line camelcase @@ -635,7 +634,6 @@ class LoggedInView extends React.Component { thirdPartyInvite={this.props.thirdPartyInvite} oobData={this.props.roomOobData} viaServers={this.props.viaServers} - eventPixelOffset={this.props.initialEventPixelOffset} key={this.props.currentRoomId || 'roomview'} disabled={this.props.middleDisabled} ConferenceHandler={this.props.ConferenceHandler} From 906d4defd5411eb148d1eae47522bd964a37ce8c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 7 Sep 2020 17:32:03 +0100 Subject: [PATCH 115/286] Convert RoomView and RoomContext to TS --- .eslintignore.errorfiles | 1 - .../structures/{RoomView.js => RoomView.tsx} | 794 ++++++++++-------- src/contexts/RoomContext.js | 25 - src/contexts/RoomContext.ts | 48 ++ src/utils/ShieldUtils.ts | 14 +- 5 files changed, 482 insertions(+), 400 deletions(-) rename src/components/structures/{RoomView.js => RoomView.tsx} (77%) delete mode 100644 src/contexts/RoomContext.js create mode 100644 src/contexts/RoomContext.ts diff --git a/.eslintignore.errorfiles b/.eslintignore.errorfiles index 1faffbbdf7..2e2a404338 100644 --- a/.eslintignore.errorfiles +++ b/.eslintignore.errorfiles @@ -2,7 +2,6 @@ src/components/structures/RoomDirectory.js src/components/structures/RoomStatusBar.js -src/components/structures/RoomView.js src/components/structures/ScrollPanel.js src/components/structures/SearchBox.js src/components/structures/UploadBar.js diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.tsx similarity index 77% rename from src/components/structures/RoomView.js rename to src/components/structures/RoomView.tsx index d98a19ebe8..05494318b9 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.tsx @@ -21,14 +21,16 @@ limitations under the License. // - Search results component // - Drag and drop -import shouldHideEvent from '../../shouldHideEvent'; - import React, {createRef} from 'react'; -import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { _t } from '../../languageHandler'; -import {RoomPermalinkCreator} from '../../utils/permalinks/Permalinks'; +import {Room} from "matrix-js-sdk/src/models/room"; +import {MatrixEvent} from "matrix-js-sdk/src/models/event"; +import {EventSubscription} from "fbemitter"; +import shouldHideEvent from '../../shouldHideEvent'; +import {_t} from '../../languageHandler'; +import {RoomPermalinkCreator} from '../../utils/permalinks/Permalinks'; +import ResizeNotifier from '../../utils/ResizeNotifier'; import ContentMessages from '../../ContentMessages'; import Modal from '../../Modal'; import * as sdk from '../../index'; @@ -39,9 +41,7 @@ import rate_limited_func from '../../ratelimitedfunc'; import * as ObjectUtils from '../../ObjectUtils'; import * as Rooms from '../../Rooms'; import eventSearch, {searchPagination} from '../../Searching'; - import {isOnlyCtrlOrCmdIgnoreShiftKeyEvent, isOnlyCtrlOrCmdKeyEvent, Key} from '../../Keyboard'; - import MainSplit from './MainSplit'; import RightPanel from './RightPanel'; import RoomViewStore from '../../stores/RoomViewStore'; @@ -53,12 +53,25 @@ import RightPanelStore from "../../stores/RightPanelStore"; import {haveTileForEvent} from "../views/rooms/EventTile"; import RoomContext from "../../contexts/RoomContext"; import MatrixClientContext from "../../contexts/MatrixClientContext"; -import { shieldStatusForRoom } from '../../utils/ShieldUtils'; +import {E2EStatus, shieldStatusForRoom} from '../../utils/ShieldUtils'; import {Action} from "../../dispatcher/actions"; import {SettingLevel} from "../../settings/SettingLevel"; +import {IMatrixClientCreds} from "../../MatrixClientPeg"; +import ScrollPanel from "./ScrollPanel"; +import TimelinePanel from "./TimelinePanel"; +import ErrorBoundary from "../views/elements/ErrorBoundary"; +import RoomPreviewBar from "../views/rooms/RoomPreviewBar"; +import ForwardMessage from "../views/rooms/ForwardMessage"; +import SearchBar from "../views/rooms/SearchBar"; +import RoomUpgradeWarningBar from "../views/rooms/RoomUpgradeWarningBar"; +import RoomRecoveryReminder from "../views/rooms/RoomRecoveryReminder"; +import PinnedEventsPanel from "../views/rooms/PinnedEventsPanel"; +import AuxPanel from "../views/rooms/AuxPanel"; +import RoomHeader from "../views/rooms/RoomHeader"; +import TintableSvg from "../views/elements/TintableSvg"; const DEBUG = false; -let debuglog = function() {}; +let debuglog = function(msg: string) {}; const BROWSER_SUPPORTS_SANDBOX = 'sandbox' in document.createElement('iframe'); @@ -67,36 +80,119 @@ if (DEBUG) { debuglog = console.log.bind(console); } -export default class RoomView extends React.Component { - static propTypes = { - ConferenceHandler: PropTypes.any, +interface IProps { + ConferenceHandler?: any; - // Called with the credentials of a registered user (if they were a ROU that - // transitioned to PWLU) - onRegistered: PropTypes.func, - - // An object representing a third party invite to join this room - // Fields: - // * inviteSignUrl (string) The URL used to join this room from an email invite - // (given as part of the link in the invite email) - // * invitedEmail (string) The email address that was invited to this room - thirdPartyInvite: PropTypes.object, - - // Any data about the room that would normally come from the homeserver - // but has been passed out-of-band, eg. the room name and avatar URL - // from an email invite (a workaround for the fact that we can't - // get this information from the HS using an email invite). - // Fields: - // * name (string) The room's name - // * avatarUrl (string) The mxc:// avatar URL for the room - // * inviterName (string) The display name of the person who - // * invited us to the room - oobData: PropTypes.object, - - // Servers the RoomView can use to try and assist joins - viaServers: PropTypes.arrayOf(PropTypes.string), + // An object representing a third party invite to join this room + // Fields: + // * inviteSignUrl (string) The URL used to join this room from an email invite + // (given as part of the link in the invite email) + // * invitedEmail (string) The email address that was invited to this room + thirdPartyInvite?: { + inviteSignUrl: string; + invitedEmail: string; }; + // Any data about the room that would normally come from the homeserver + // but has been passed out-of-band, eg. the room name and avatar URL + // from an email invite (a workaround for the fact that we can't + // get this information from the HS using an email invite). + // Fields: + // * name (string) The room's name + // * avatarUrl (string) The mxc:// avatar URL for the room + // * inviterName (string) The display name of the person who + // * invited us to the room + oobData?: any; + + // Servers the RoomView can use to try and assist joins + viaServers?: string[]; + + autoJoin?: boolean; + disabled?: boolean; + resizeNotifier: ResizeNotifier; + + // Called with the credentials of a registered user (if they were a ROU that transitioned to PWLU) + onRegistered?(credentials: IMatrixClientCreds): void; +} + +export interface IState { + room?: Room; + roomId?: string; + roomAlias?: string; + roomLoading: boolean; + peekLoading: boolean; + shouldPeek: boolean; + // used to trigger a rerender in TimelinePanel once the members are loaded, + // so RR are rendered again (now with the members available), ... + membersLoaded: boolean; + // The event to be scrolled to initially + initialEventId?: string; + // The offset in pixels from the event with which to scroll vertically + initialEventPixelOffset?: number; + // Whether to highlight the event scrolled to + isInitialEventHighlighted?: boolean; + forwardingEvent?: MatrixEvent; + numUnreadMessages: number; + draggingFile: boolean; + searching: boolean; + searchTerm?: string; + searchScope?: "All" | "Room"; + searchResults?: any; + searchHighlights?: string[]; + searchInProgress?: boolean; + callState?: string; + guestsCanJoin: boolean; + canPeek: boolean; + showApps: boolean; + isAlone: boolean; + isPeeking: boolean; + showingPinned: boolean; + showReadReceipts: boolean; + showRightPanel: boolean; + // error object, as from the matrix client/server API + // If we failed to load information about the room, + // store the error here. + roomLoadError?: Error; + // Have we sent a request to join the room that we're waiting to complete? + joining: boolean; + // this is true if we are fully scrolled-down, and are looking at + // the end of the live timeline. It has the effect of hiding the + // 'scroll to bottom' knob, among a couple of other things. + atEndOfLiveTimeline: boolean; + // used by componentDidUpdate to avoid unnecessary checks + atEndOfLiveTimelineInit: boolean; + showTopUnreadMessagesBar: boolean; + auxPanelMaxHeight?: number; + statusBarVisible: boolean; + // We load this later by asking the js-sdk to suggest a version for us. + // This object is the result of Room#getRecommendedVersion() + upgradeRecommendation?: any; + canReact: boolean; + canReply: boolean; + useIRCLayout: boolean; + matrixClientIsReady: boolean; + showUrlPreview?: boolean; + e2eStatus?: E2EStatus; + displayConfCallNotification?: boolean; + rejecting?: boolean; + rejectError?: Error; +} + +export default class RoomView extends React.Component { + private readonly dispatcherRef: string; + private readonly roomStoreToken: EventSubscription; + private readonly rightPanelStoreToken: EventSubscription; + private readonly showReadReceiptsWatchRef: string; + private readonly layoutWatcherRef: string; + + private unmounted = false; + private permalinkCreators: Record = {}; + private searchId: number; + + private roomView = createRef(); + private searchResultsPanel = createRef(); + private messagePanel: TimelinePanel; + static contextType = MatrixClientContext; constructor(props, context) { @@ -104,26 +200,11 @@ export default class RoomView extends React.Component { const llMembers = this.context.hasLazyLoadMembersEnabled(); this.state = { - room: null, roomId: null, roomLoading: true, peekLoading: false, shouldPeek: true, - - // Media limits for uploading. - mediaConfig: undefined, - - // used to trigger a rerender in TimelinePanel once the members are loaded, - // so RR are rendered again (now with the members available), ... membersLoaded: !llMembers, - // The event to be scrolled to initially - initialEventId: null, - // The offset in pixels from the event with which to scroll vertically - initialEventPixelOffset: null, - // Whether to highlight the event scrolled to - isInitialEventHighlighted: null, - - forwardingEvent: null, numUnreadMessages: 0, draggingFile: false, searching: false, @@ -137,36 +218,14 @@ export default class RoomView extends React.Component { showingPinned: false, showReadReceipts: true, showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom, - - // error object, as from the matrix client/server API - // If we failed to load information about the room, - // store the error here. - roomLoadError: null, - - // Have we sent a request to join the room that we're waiting to complete? joining: false, - - // this is true if we are fully scrolled-down, and are looking at - // the end of the live timeline. It has the effect of hiding the - // 'scroll to bottom' knob, among a couple of other things. atEndOfLiveTimeline: true, - atEndOfLiveTimelineInit: false, // used by componentDidUpdate to avoid unnecessary checks - + atEndOfLiveTimelineInit: false, showTopUnreadMessagesBar: false, - - auxPanelMaxHeight: undefined, - statusBarVisible: false, - - // We load this later by asking the js-sdk to suggest a version for us. - // This object is the result of Room#getRecommendedVersion() - upgradeRecommendation: null, - canReact: false, canReply: false, - useIRCLayout: SettingsStore.getValue("useIRCLayout"), - matrixClientIsReady: this.context && this.context.isInitialSyncComplete(), }; @@ -184,31 +243,28 @@ export default class RoomView extends React.Component { this.context.on("userTrustStatusChanged", this.onUserVerificationChanged); this.context.on("crossSigning.keysChanged", this.onCrossSigningKeysChanged); // Start listening for RoomViewStore updates - this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate); - this._rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this._onRightPanelStoreUpdate); + this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate); + this.rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this.onRightPanelStoreUpdate); - WidgetEchoStore.on('update', this._onWidgetEchoStoreUpdate); - this._showReadReceiptsWatchRef = SettingsStore.watchSetting("showReadReceipts", null, - this._onReadReceiptsChange); - - this._roomView = createRef(); - this._searchResultsPanel = createRef(); - - this._layoutWatcherRef = SettingsStore.watchSetting("useIRCLayout", null, this.onLayoutChange); + WidgetEchoStore.on('update', this.onWidgetEchoStoreUpdate); + this.showReadReceiptsWatchRef = SettingsStore.watchSetting("showReadReceipts", null, + this.onReadReceiptsChange); + this.layoutWatcherRef = SettingsStore.watchSetting("useIRCLayout", null, this.onLayoutChange); } // TODO: [REACT-WARNING] Move into constructor + // eslint-disable-next-line camelcase UNSAFE_componentWillMount() { - this._onRoomViewStoreUpdate(true); + this.onRoomViewStoreUpdate(true); } - _onReadReceiptsChange = () => { + private onReadReceiptsChange = () => { this.setState({ showReadReceipts: SettingsStore.getValue("showReadReceipts", this.state.roomId), }); }; - _onRoomViewStoreUpdate = initial => { + private onRoomViewStoreUpdate = (initial?: boolean) => { if (this.unmounted) { return; } @@ -230,7 +286,7 @@ export default class RoomView extends React.Component { const roomId = RoomViewStore.getRoomId(); - const newState = { + const newState: Pick = { roomId, roomAlias: RoomViewStore.getRoomAlias(), roomLoading: RoomViewStore.isRoomLoading(), @@ -266,8 +322,8 @@ export default class RoomView extends React.Component { if (initial) { newState.room = this.context.getRoom(newState.roomId); if (newState.room) { - newState.showApps = this._shouldShowApps(newState.room); - this._onRoomLoaded(newState.room); + newState.showApps = this.shouldShowApps(newState.room); + this.onRoomLoaded(newState.room); } } @@ -300,48 +356,47 @@ export default class RoomView extends React.Component { // callback because this would prevent the setStates from being batched, // ie. cause it to render RoomView twice rather than the once that is necessary. if (initial) { - this._setupRoom(newState.room, newState.roomId, newState.joining, newState.shouldPeek); + this.setupRoom(newState.room, newState.roomId, newState.joining, newState.shouldPeek); } }; - _getRoomId() { - // According to `_onRoomViewStoreUpdate`, `state.roomId` can be null + private getRoomId = () => { + // According to `onRoomViewStoreUpdate`, `state.roomId` can be null // if we have a room alias we haven't resolved yet. To work around this, // first we'll try the room object if it's there, and then fallback to // the bare room ID. (We may want to update `state.roomId` after // resolving aliases, so we could always trust it.) return this.state.room ? this.state.room.roomId : this.state.roomId; - } + }; - _getPermalinkCreatorForRoom(room) { - if (!this._permalinkCreators) this._permalinkCreators = {}; - if (this._permalinkCreators[room.roomId]) return this._permalinkCreators[room.roomId]; + private getPermalinkCreatorForRoom(room: Room) { + if (this.permalinkCreators[room.roomId]) return this.permalinkCreators[room.roomId]; - this._permalinkCreators[room.roomId] = new RoomPermalinkCreator(room); + this.permalinkCreators[room.roomId] = new RoomPermalinkCreator(room); if (this.state.room && room.roomId === this.state.room.roomId) { // We want to watch for changes in the creator for the primary room in the view, but // don't need to do so for search results. - this._permalinkCreators[room.roomId].start(); + this.permalinkCreators[room.roomId].start(); } else { - this._permalinkCreators[room.roomId].load(); + this.permalinkCreators[room.roomId].load(); } - return this._permalinkCreators[room.roomId]; + return this.permalinkCreators[room.roomId]; } - _stopAllPermalinkCreators() { - if (!this._permalinkCreators) return; - for (const roomId of Object.keys(this._permalinkCreators)) { - this._permalinkCreators[roomId].stop(); + private stopAllPermalinkCreators() { + if (!this.permalinkCreators) return; + for (const roomId of Object.keys(this.permalinkCreators)) { + this.permalinkCreators[roomId].stop(); } } - _onWidgetEchoStoreUpdate = () => { + private onWidgetEchoStoreUpdate = () => { this.setState({ - showApps: this._shouldShowApps(this.state.room), + showApps: this.shouldShowApps(this.state.room), }); }; - _setupRoom(room, roomId, joining, shouldPeek) { + private setupRoom(room: Room, roomId: string, joining: boolean, shouldPeek: boolean) { // if this is an unknown room then we're in one of three states: // - This is a room we can peek into (search engine) (we can /peek) // - This is a room we can publicly join or were invited to. (we can /join) @@ -374,7 +429,7 @@ export default class RoomView extends React.Component { room: room, peekLoading: false, }); - this._onRoomLoaded(room); + this.onRoomLoaded(room); }).catch((err) => { if (this.unmounted) { return; @@ -405,7 +460,7 @@ export default class RoomView extends React.Component { } } - _shouldShowApps(room) { + private shouldShowApps(room: Room) { if (!BROWSER_SUPPORTS_SANDBOX) return false; // Check if user has previously chosen to hide the app drawer for this @@ -419,13 +474,13 @@ export default class RoomView extends React.Component { } componentDidMount() { - const call = this._getCallForRoom(); + const call = this.getCallForRoom(); const callState = call ? call.call_state : "ended"; this.setState({ callState: callState, }); - this._updateConfCallNotification(); + this.updateConfCallNotification(); window.addEventListener('beforeunload', this.onPageUnload); if (this.props.resizeNotifier) { @@ -442,8 +497,8 @@ export default class RoomView extends React.Component { } componentDidUpdate() { - if (this._roomView.current) { - const roomView = this._roomView.current; + if (this.roomView.current) { + const roomView = this.roomView.current; if (!roomView.ondrop) { roomView.addEventListener('drop', this.onDrop); roomView.addEventListener('dragover', this.onDragOver); @@ -457,10 +512,10 @@ export default class RoomView extends React.Component { // in render() prevents the ref from being set on first mount, so we try and // catch the messagePanel when it does mount. Because we only want the ref once, // we use a boolean flag to avoid duplicate work. - if (this._messagePanel && !this.state.atEndOfLiveTimelineInit) { + if (this.messagePanel && !this.state.atEndOfLiveTimelineInit) { this.setState({ atEndOfLiveTimelineInit: true, - atEndOfLiveTimeline: this._messagePanel.isAtEndOfLiveTimeline(), + atEndOfLiveTimeline: this.messagePanel.isAtEndOfLiveTimeline(), }); } } @@ -474,7 +529,7 @@ export default class RoomView extends React.Component { // update the scroll map before we get unmounted if (this.state.roomId) { - RoomScrollStateStore.setScrollState(this.state.roomId, this._getScrollState()); + RoomScrollStateStore.setScrollState(this.state.roomId, this.getScrollState()); } if (this.state.shouldPeek) { @@ -482,14 +537,14 @@ export default class RoomView extends React.Component { } // stop tracking room changes to format permalinks - this._stopAllPermalinkCreators(); + this.stopAllPermalinkCreators(); - if (this._roomView.current) { + if (this.roomView.current) { // disconnect the D&D event listeners from the room view. This // is really just for hygiene - we're going to be // deleted anyway, so it doesn't matter if the event listeners // don't get cleaned up. - const roomView = this._roomView.current; + const roomView = this.roomView.current; roomView.removeEventListener('drop', this.onDrop); roomView.removeEventListener('dragover', this.onDragOver); roomView.removeEventListener('dragleave', this.onDragLeaveOrEnd); @@ -519,55 +574,54 @@ export default class RoomView extends React.Component { document.removeEventListener("keydown", this.onNativeKeyDown); // Remove RoomStore listener - if (this._roomStoreToken) { - this._roomStoreToken.remove(); + if (this.roomStoreToken) { + this.roomStoreToken.remove(); } // Remove RightPanelStore listener - if (this._rightPanelStoreToken) { - this._rightPanelStoreToken.remove(); + if (this.rightPanelStoreToken) { + this.rightPanelStoreToken.remove(); } - WidgetEchoStore.removeListener('update', this._onWidgetEchoStoreUpdate); + WidgetEchoStore.removeListener('update', this.onWidgetEchoStoreUpdate); - if (this._showReadReceiptsWatchRef) { - SettingsStore.unwatchSetting(this._showReadReceiptsWatchRef); - this._showReadReceiptsWatchRef = null; + if (this.showReadReceiptsWatchRef) { + SettingsStore.unwatchSetting(this.showReadReceiptsWatchRef); } // cancel any pending calls to the rate_limited_funcs - this._updateRoomMembers.cancelPendingCall(); + this.updateRoomMembers.cancelPendingCall(); // no need to do this as Dir & Settings are now overlays. It just burnt CPU. // console.log("Tinter.tint from RoomView.unmount"); // Tinter.tint(); // reset colourscheme - SettingsStore.unwatchSetting(this._layoutWatcherRef); + SettingsStore.unwatchSetting(this.layoutWatcherRef); } - onLayoutChange = () => { + private onLayoutChange = () => { this.setState({ useIRCLayout: SettingsStore.getValue("useIRCLayout"), }); }; - _onRightPanelStoreUpdate = () => { + private onRightPanelStoreUpdate = () => { this.setState({ showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom, }); }; - onPageUnload = event => { + private onPageUnload = event => { if (ContentMessages.sharedInstance().getCurrentUploads().length > 0) { return event.returnValue = _t("You seem to be uploading files, are you sure you want to quit?"); - } else if (this._getCallForRoom() && this.state.callState !== 'ended') { + } else if (this.getCallForRoom() && this.state.callState !== 'ended') { return event.returnValue = _t("You seem to be in a call, are you sure you want to quit?"); } }; // we register global shortcuts here, they *must not conflict* with local shortcuts elsewhere or both will fire - onNativeKeyDown = ev => { + private onNativeKeyDown = ev => { let handled = false; const ctrlCmdOnly = isOnlyCtrlOrCmdKeyEvent(ev); @@ -593,13 +647,13 @@ export default class RoomView extends React.Component { } }; - onReactKeyDown = ev => { + private onReactKeyDown = ev => { let handled = false; switch (ev.key) { case Key.ESCAPE: if (!ev.altKey && !ev.ctrlKey && !ev.shiftKey && !ev.metaKey) { - this._messagePanel.forgetReadMarker(); + this.messagePanel.forgetReadMarker(); this.jumpToLiveTimeline(); handled = true; } @@ -624,20 +678,21 @@ export default class RoomView extends React.Component { } }; - onAction = payload => { + private onAction = payload => { switch (payload.action) { case 'message_send_failed': case 'message_sent': - this._checkIfAlone(this.state.room); + this.checkIfAlone(this.state.room); break; case 'post_sticker_message': - this.injectSticker( - payload.data.content.url, - payload.data.content.info, - payload.data.description || payload.data.name); - break; + this.injectSticker( + payload.data.content.url, + payload.data.content.info, + payload.data.description || payload.data.name); + break; case 'picture_snapshot': - ContentMessages.sharedInstance().sendContentListToRoom([payload.file], this.state.room.roomId, this.context); + ContentMessages.sharedInstance().sendContentListToRoom( + [payload.file], this.state.room.roomId, this.context); break; case 'notifier_enabled': case 'upload_started': @@ -645,7 +700,7 @@ export default class RoomView extends React.Component { case 'upload_canceled': this.forceUpdate(); break; - case 'call_state': + case 'call_state': { // don't filter out payloads for room IDs other than props.room because // we may be interested in the conf 1:1 room @@ -653,24 +708,22 @@ export default class RoomView extends React.Component { return; } - var call = this._getCallForRoom(); - var callState; + const call = this.getCallForRoom(); + let callState = "ended"; if (call) { callState = call.call_state; - } else { - callState = "ended"; } // possibly remove the conf call notification if we're now in // the conf - this._updateConfCallNotification(); + this.updateConfCallNotification(); this.setState({ callState: callState, }); - break; + } case 'appsDrawer': this.setState({ showApps: payload.show, @@ -703,14 +756,14 @@ export default class RoomView extends React.Component { matrixClientIsReady: this.context && this.context.isInitialSyncComplete(), }, () => { // send another "initial" RVS update to trigger peeking if needed - this._onRoomViewStoreUpdate(true); + this.onRoomViewStoreUpdate(true); }); } break; } }; - onRoomTimeline = (ev, room, toStartOfTimeline, removed, data) => { + private onRoomTimeline = (ev: MatrixEvent, room: Room, toStartOfTimeline: boolean, removed, data) => { if (this.unmounted) return; // ignore events for other rooms @@ -721,11 +774,11 @@ export default class RoomView extends React.Component { if (data.timeline.getTimelineSet() !== room.getUnfilteredTimelineSet()) return; if (ev.getType() === "org.matrix.room.preview_urls") { - this._updatePreviewUrlVisibility(room); + this.updatePreviewUrlVisibility(room); } if (ev.getType() === "m.room.encryption") { - this._updateE2EStatus(room); + this.updateE2EStatus(room); } // ignore anything but real-time updates at the end of the room: @@ -748,49 +801,49 @@ export default class RoomView extends React.Component { } }; - onRoomName = room => { + private onRoomName = (room: Room) => { if (this.state.room && room.roomId == this.state.room.roomId) { this.forceUpdate(); } }; - onRoomRecoveryReminderDontAskAgain = () => { + private onRoomRecoveryReminderDontAskAgain = () => { // Called when the option to not ask again is set: // force an update to hide the recovery reminder this.forceUpdate(); }; - onKeyBackupStatus = () => { + private onKeyBackupStatus = () => { // Key backup status changes affect whether the in-room recovery // reminder is displayed. this.forceUpdate(); }; - canResetTimeline = () => { - if (!this._messagePanel) { + public canResetTimeline = () => { + if (!this.messagePanel) { return true; } - return this._messagePanel.canResetTimeline(); + return this.messagePanel.canResetTimeline(); }; // called when state.room is first initialised (either at initial load, // after a successful peek, or after we join the room). - _onRoomLoaded = room => { - this._calculatePeekRules(room); - this._updatePreviewUrlVisibility(room); - this._loadMembersIfJoined(room); - this._calculateRecommendedVersion(room); - this._updateE2EStatus(room); - this._updatePermissions(room); + private onRoomLoaded = (room: Room) => { + this.calculatePeekRules(room); + this.updatePreviewUrlVisibility(room); + this.loadMembersIfJoined(room); + this.calculateRecommendedVersion(room); + this.updateE2EStatus(room); + this.updatePermissions(room); }; - async _calculateRecommendedVersion(room) { + private async calculateRecommendedVersion(room: Room) { this.setState({ upgradeRecommendation: await room.getRecommendedVersion(), }); } - async _loadMembersIfJoined(room) { + private async loadMembersIfJoined(room: Room) { // lazy load members if enabled if (this.context.hasLazyLoadMembersEnabled()) { if (room && room.getMyMembership() === 'join') { @@ -809,7 +862,7 @@ export default class RoomView extends React.Component { } } - _calculatePeekRules(room) { + private calculatePeekRules(room: Room) { const guestAccessEvent = room.currentState.getStateEvents("m.room.guest_access", ""); if (guestAccessEvent && guestAccessEvent.getContent().guest_access === "can_join") { this.setState({ @@ -825,7 +878,7 @@ export default class RoomView extends React.Component { } } - _updatePreviewUrlVisibility({roomId}) { + private updatePreviewUrlVisibility({roomId}: Room) { // URL Previews in E2EE rooms can be a privacy leak so use a different setting which is per-room explicit const key = this.context.isRoomEncrypted(roomId) ? 'urlPreviewsEnabled_e2ee' : 'urlPreviewsEnabled'; this.setState({ @@ -833,41 +886,41 @@ export default class RoomView extends React.Component { }); } - onRoom = room => { + private onRoom = (room: Room) => { if (!room || room.roomId !== this.state.roomId) { return; } this.setState({ room: room, }, () => { - this._onRoomLoaded(room); + this.onRoomLoaded(room); }); }; - onDeviceVerificationChanged = (userId, device) => { + private onDeviceVerificationChanged = (userId: string, device: object) => { const room = this.state.room; if (!room.currentState.getMember(userId)) { return; } - this._updateE2EStatus(room); + this.updateE2EStatus(room); }; - onUserVerificationChanged = (userId, _trustStatus) => { + private onUserVerificationChanged = (userId: string, trustStatus: object) => { const room = this.state.room; if (!room || !room.currentState.getMember(userId)) { return; } - this._updateE2EStatus(room); + this.updateE2EStatus(room); }; - onCrossSigningKeysChanged = () => { + private onCrossSigningKeysChanged = () => { const room = this.state.room; if (room) { - this._updateE2EStatus(room); + this.updateE2EStatus(room); } }; - async _updateE2EStatus(room) { + private async updateE2EStatus(room: Room) { if (!this.context.isRoomEncrypted(room.roomId)) { return; } @@ -876,7 +929,7 @@ export default class RoomView extends React.Component { // so we don't know what the answer is. Let's error on the safe side and show // a warning for this case. this.setState({ - e2eStatus: "warning", + e2eStatus: E2EStatus.Warning, }); return; } @@ -887,7 +940,7 @@ export default class RoomView extends React.Component { }); } - updateTint() { + private updateTint() { const room = this.state.room; if (!room) return; @@ -896,15 +949,15 @@ export default class RoomView extends React.Component { Tinter.tint(colorScheme.primary_color, colorScheme.secondary_color); } - onAccountData = event => { + private onAccountData = (event: MatrixEvent) => { const type = event.getType(); if ((type === "org.matrix.preview_urls" || type === "im.vector.web.settings") && this.state.room) { // non-e2ee url previews are stored in legacy event type `org.matrix.room.preview_urls` - this._updatePreviewUrlVisibility(this.state.room); + this.updatePreviewUrlVisibility(this.state.room); } }; - onRoomAccountData = (event, room) => { + private onRoomAccountData = (event: MatrixEvent, room: Room) => { if (room.roomId == this.state.roomId) { const type = event.getType(); if (type === "org.matrix.room.color_scheme") { @@ -914,21 +967,21 @@ export default class RoomView extends React.Component { Tinter.tint(colorScheme.primary_color, colorScheme.secondary_color); } else if (type === "org.matrix.room.preview_urls" || type === "im.vector.web.settings") { // non-e2ee url previews are stored in legacy event type `org.matrix.room.preview_urls` - this._updatePreviewUrlVisibility(room); + this.updatePreviewUrlVisibility(room); } } }; - onRoomStateEvents = (ev, state) => { + private onRoomStateEvents = (ev: MatrixEvent, state) => { // ignore if we don't have a room yet if (!this.state.room || this.state.room.roomId !== state.roomId) { return; } - this._updatePermissions(this.state.room); + this.updatePermissions(this.state.room); }; - onRoomStateMember = (ev, state, member) => { + private onRoomStateMember = (ev: MatrixEvent, state, member) => { // ignore if we don't have a room yet if (!this.state.room) { return; @@ -939,18 +992,18 @@ export default class RoomView extends React.Component { return; } - this._updateRoomMembers(member); + this.updateRoomMembers(member); }; - onMyMembership = (room, membership, oldMembership) => { + private onMyMembership = (room: Room, membership: string, oldMembership: string) => { if (room.roomId === this.state.roomId) { this.forceUpdate(); - this._loadMembersIfJoined(room); - this._updatePermissions(room); + this.loadMembersIfJoined(room); + this.updatePermissions(room); } }; - _updatePermissions(room) { + private updatePermissions(room: Room) { if (room) { const me = this.context.getUserId(); const canReact = room.getMyMembership() === "join" && room.currentState.maySendEvent("m.reaction", me); @@ -960,13 +1013,12 @@ export default class RoomView extends React.Component { } } - // rate limited because a power level change will emit an event for every - // member in the room. - _updateRoomMembers = rate_limited_func((dueToMember) => { + // rate limited because a power level change will emit an event for every member in the room. + private updateRoomMembers = rate_limited_func((dueToMember) => { // a member state changed in this room // refresh the conf call notification state - this._updateConfCallNotification(); - this._updateDMState(); + this.updateConfCallNotification(); + this.updateDMState(); let memberCountInfluence = 0; if (dueToMember && dueToMember.membership === "invite" && this.state.room.getInvitedMemberCount() === 0) { @@ -974,15 +1026,15 @@ export default class RoomView extends React.Component { // count by 1 to counteract this. memberCountInfluence = 1; } - this._checkIfAlone(this.state.room, memberCountInfluence); + this.checkIfAlone(this.state.room, memberCountInfluence); - this._updateE2EStatus(this.state.room); + this.updateE2EStatus(this.state.room); }, 500); - _checkIfAlone(room, countInfluence) { + private checkIfAlone(room: Room, countInfluence?: number) { let warnedAboutLonelyRoom = false; if (localStorage) { - warnedAboutLonelyRoom = localStorage.getItem('mx_user_alone_warned_' + this.state.room.roomId); + warnedAboutLonelyRoom = !!localStorage.getItem('mx_user_alone_warned_' + this.state.room.roomId); } if (warnedAboutLonelyRoom) { if (this.state.isAlone) this.setState({isAlone: false}); @@ -994,7 +1046,7 @@ export default class RoomView extends React.Component { this.setState({isAlone: joinedOrInvitedMemberCount === 1}); } - _updateConfCallNotification() { + private updateConfCallNotification() { const room = this.state.room; if (!room || !this.props.ConferenceHandler) { return; @@ -1018,7 +1070,7 @@ export default class RoomView extends React.Component { }); } - _updateDMState() { + private updateDMState() { const room = this.state.room; if (room.getMyMembership() != "join") { return; @@ -1029,7 +1081,7 @@ export default class RoomView extends React.Component { } } - onSearchResultsFillRequest = backwards => { + private onSearchResultsFillRequest = (backwards: boolean) => { if (!backwards) { return Promise.resolve(false); } @@ -1037,14 +1089,14 @@ export default class RoomView extends React.Component { if (this.state.searchResults.next_batch) { debuglog("requesting more search results"); const searchPromise = searchPagination(this.state.searchResults); - return this._handleSearchResult(searchPromise); + return this.handleSearchResult(searchPromise); } else { debuglog("no more search results"); return Promise.resolve(false); } }; - onInviteButtonClick = () => { + private onInviteButtonClick = () => { // call AddressPickerDialog dis.dispatch({ action: 'view_invite', @@ -1053,14 +1105,14 @@ export default class RoomView extends React.Component { this.setState({isAlone: false}); // there's a good chance they'll invite someone }; - onStopAloneWarningClick = () => { + private onStopAloneWarningClick = () => { if (localStorage) { - localStorage.setItem('mx_user_alone_warned_' + this.state.room.roomId, true); + localStorage.setItem('mx_user_alone_warned_' + this.state.room.roomId, String(true)); } this.setState({isAlone: false}); }; - onJoinButtonClicked = ev => { + private onJoinButtonClicked = () => { // If the user is a ROU, allow them to transition to a PWLU if (this.context && this.context.isGuest()) { // Join this room once the user has registered and logged in @@ -1069,7 +1121,7 @@ export default class RoomView extends React.Component { action: 'do_after_sync_prepared', deferred_action: { action: 'view_room', - room_id: this._getRoomId(), + room_id: this.getRoomId(), }, }); @@ -1121,8 +1173,8 @@ export default class RoomView extends React.Component { } }; - onMessageListScroll = ev => { - if (this._messagePanel.isAtEndOfLiveTimeline()) { + private onMessageListScroll = ev => { + if (this.messagePanel.isAtEndOfLiveTimeline()) { this.setState({ numUnreadMessages: 0, atEndOfLiveTimeline: true, @@ -1132,10 +1184,10 @@ export default class RoomView extends React.Component { atEndOfLiveTimeline: false, }); } - this._updateTopUnreadMessagesBar(); + this.updateTopUnreadMessagesBar(); }; - onDragOver = ev => { + private onDragOver = ev => { ev.stopPropagation(); ev.preventDefault(); @@ -1154,7 +1206,7 @@ export default class RoomView extends React.Component { } }; - onDrop = ev => { + private onDrop = ev => { ev.stopPropagation(); ev.preventDefault(); ContentMessages.sharedInstance().sendContentListToRoom( @@ -1164,13 +1216,13 @@ export default class RoomView extends React.Component { dis.fire(Action.FocusComposer); }; - onDragLeaveOrEnd = ev => { + private onDragLeaveOrEnd = ev => { ev.stopPropagation(); ev.preventDefault(); this.setState({ draggingFile: false }); }; - injectSticker(url, info, text) { + private injectSticker(url, info, text) { if (this.context.isGuest()) { dis.dispatch({action: 'require_registration'}); return; @@ -1185,7 +1237,7 @@ export default class RoomView extends React.Component { }); } - onSearch = (term, scope) => { + private onSearch = (term: string, scope) => { this.setState({ searchTerm: term, searchScope: scope, @@ -1195,8 +1247,8 @@ export default class RoomView extends React.Component { // if we already have a search panel, we need to tell it to forget // about its scroll state. - if (this._searchResultsPanel.current) { - this._searchResultsPanel.current.resetScrollState(); + if (this.searchResultsPanel.current) { + this.searchResultsPanel.current.resetScrollState(); } // make sure that we don't end up showing results from @@ -1210,12 +1262,10 @@ export default class RoomView extends React.Component { debuglog("sending search request"); const searchPromise = eventSearch(term, roomId); - this._handleSearchResult(searchPromise); + this.handleSearchResult(searchPromise); }; - _handleSearchResult(searchPromise) { - const self = this; - + private handleSearchResult(searchPromise: Promise) { // keep a record of the current search id, so that if the search terms // change before we get a response, we can ignore the results. const localSearchId = this.searchId; @@ -1224,9 +1274,9 @@ export default class RoomView extends React.Component { searchInProgress: true, }); - return searchPromise.then(function(results) { + return searchPromise.then((results) => { debuglog("search complete"); - if (self.unmounted || !self.state.searching || self.searchId != localSearchId) { + if (this.unmounted || !this.state.searching || this.searchId != localSearchId) { console.error("Discarding stale search results"); return; } @@ -1238,8 +1288,8 @@ export default class RoomView extends React.Component { // whether it was used by the search engine or not. let highlights = results.highlights; - if (highlights.indexOf(self.state.searchTerm) < 0) { - highlights = highlights.concat(self.state.searchTerm); + if (highlights.indexOf(this.state.searchTerm) < 0) { + highlights = highlights.concat(this.state.searchTerm); } // For overlapping highlights, @@ -1248,25 +1298,26 @@ export default class RoomView extends React.Component { return b.length - a.length; }); - self.setState({ + this.setState({ searchHighlights: highlights, searchResults: results, }); - }, function(error) { + }, (error) => { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Search failed", error); Modal.createTrackedDialog('Search failed', '', ErrorDialog, { title: _t("Search failed"), - description: ((error && error.message) ? error.message : _t("Server may be unavailable, overloaded, or search timed out :(")), + description: ((error && error.message) ? error.message : + _t("Server may be unavailable, overloaded, or search timed out :(")), }); - }).finally(function() { - self.setState({ + }).finally(() => { + this.setState({ searchInProgress: false, }); }); } - getSearchResultTiles() { + private getSearchResultTiles() { const SearchResultTile = sdk.getComponent('rooms.SearchResultTile'); const Spinner = sdk.getComponent("elements.Spinner"); @@ -1277,20 +1328,20 @@ export default class RoomView extends React.Component { if (this.state.searchInProgress) { ret.push(
  • - -
  • ); + + ); } if (!this.state.searchResults.next_batch) { if (this.state.searchResults.results.length == 0) { ret.push(
  • -

    { _t("No results") }

    -
  • , +

    { _t("No results") }

    + , ); } else { ret.push(
  • -

    { _t("No more results") }

    -
  • , +

    { _t("No more results") }

    + , ); } } @@ -1298,7 +1349,7 @@ export default class RoomView extends React.Component { // once dynamic content in the search results load, make the scrollPanel check // the scroll offsets. const onHeightChanged = () => { - const scrollPanel = this._searchResultsPanel.current; + const scrollPanel = this.searchResultsPanel.current; if (scrollPanel) { scrollPanel.checkScroll(); } @@ -1330,36 +1381,38 @@ export default class RoomView extends React.Component { if (this.state.searchScope === 'All') { if (roomId !== lastRoomId) { ret.push(
  • -

    { _t("Room") }: { room.name }

    -
  • ); +

    { _t("Room") }: { room.name }

    + ); lastRoomId = roomId; } } const resultLink = "#/room/"+roomId+"/"+mxEv.getId(); - ret.push(); + ret.push(); } return ret; } - onPinnedClick = () => { + private onPinnedClick = () => { const nowShowingPinned = !this.state.showingPinned; const roomId = this.state.room.roomId; this.setState({showingPinned: nowShowingPinned, searching: false}); SettingsStore.setValue("PinnedEvents.isOpen", roomId, SettingLevel.ROOM_DEVICE, nowShowingPinned); }; - onSettingsClick = () => { + private onSettingsClick = () => { dis.dispatch({ action: 'open_room_settings' }); }; - onCancelClick = () => { + private onCancelClick = () => { console.log("updateTint from onCancelClick"); this.updateTint(); if (this.state.forwardingEvent) { @@ -1371,31 +1424,30 @@ export default class RoomView extends React.Component { dis.fire(Action.FocusComposer); }; - onLeaveClick = () => { + private onLeaveClick = () => { dis.dispatch({ action: 'leave_room', room_id: this.state.room.roomId, }); }; - onForgetClick = () => { + private onForgetClick = () => { dis.dispatch({ action: 'forget_room', room_id: this.state.room.roomId, }); }; - onRejectButtonClicked = ev => { - const self = this; + private onRejectButtonClicked = ev => { this.setState({ rejecting: true, }); - this.context.leave(this.state.roomId).then(function() { + this.context.leave(this.state.roomId).then(() => { dis.dispatch({ action: 'view_next_room' }); - self.setState({ + this.setState({ rejecting: false, }); - }, function(error) { + }, (error) => { console.error("Failed to reject invite: %s", error); const msg = error.message ? error.message : JSON.stringify(error); @@ -1405,14 +1457,14 @@ export default class RoomView extends React.Component { description: msg, }); - self.setState({ + this.setState({ rejecting: false, rejectError: error, }); }); }; - onRejectAndIgnoreClick = async () => { + private onRejectAndIgnoreClick = async () => { this.setState({ rejecting: true, }); @@ -1439,14 +1491,14 @@ export default class RoomView extends React.Component { description: msg, }); - self.setState({ + this.setState({ rejecting: false, rejectError: error, }); } }; - onRejectThreepidInviteButtonClicked = ev => { + private onRejectThreepidInviteButtonClicked = ev => { // We can reject 3pid invites in the same way that we accept them, // using /leave rather than /join. In the short term though, we // just ignore them. @@ -1454,14 +1506,14 @@ export default class RoomView extends React.Component { dis.fire(Action.ViewRoomDirectory); }; - onSearchClick = () => { + private onSearchClick = () => { this.setState({ searching: !this.state.searching, showingPinned: false, }); }; - onCancelSearchClick = () => { + private onCancelSearchClick = () => { this.setState({ searching: false, searchResults: null, @@ -1469,29 +1521,29 @@ export default class RoomView extends React.Component { }; // jump down to the bottom of this room, where new events are arriving - jumpToLiveTimeline = () => { - this._messagePanel.jumpToLiveTimeline(); + private jumpToLiveTimeline = () => { + this.messagePanel.jumpToLiveTimeline(); dis.fire(Action.FocusComposer); }; // jump up to wherever our read marker is - jumpToReadMarker = () => { - this._messagePanel.jumpToReadMarker(); + private jumpToReadMarker = () => { + this.messagePanel.jumpToReadMarker(); }; // update the read marker to match the read-receipt - forgetReadMarker = ev => { + private forgetReadMarker = ev => { ev.stopPropagation(); - this._messagePanel.forgetReadMarker(); + this.messagePanel.forgetReadMarker(); }; // decide whether or not the top 'unread messages' bar should be shown - _updateTopUnreadMessagesBar = () => { - if (!this._messagePanel) { + private updateTopUnreadMessagesBar = () => { + if (!this.messagePanel) { return; } - const showBar = this._messagePanel.canJumpToReadMarker(); + const showBar = this.messagePanel.canJumpToReadMarker(); if (this.state.showTopUnreadMessagesBar != showBar) { this.setState({showTopUnreadMessagesBar: showBar}); } @@ -1500,8 +1552,8 @@ export default class RoomView extends React.Component { // get the current scroll position of the room, so that it can be // restored when we switch back to it. // - _getScrollState() { - const messagePanel = this._messagePanel; + private getScrollState() { + const messagePanel = this.messagePanel; if (!messagePanel) return null; // if we're following the live timeline, we want to return null; that @@ -1537,7 +1589,7 @@ export default class RoomView extends React.Component { }; } - onResize = () => { + private onResize = () => { // It seems flexbox doesn't give us a way to constrain the auxPanel height to have // a minimum of the height of the video element, whilst also capping it from pushing out the page // so we have to do it via JS instead. In this implementation we cap the height by putting @@ -1557,15 +1609,15 @@ export default class RoomView extends React.Component { this.setState({auxPanelMaxHeight: auxPanelMaxHeight}); }; - onFullscreenClick = () => { + private onFullscreenClick = () => { dis.dispatch({ action: 'video_fullscreen', fullscreen: true, }, true); }; - onMuteAudioClick = () => { - const call = this._getCallForRoom(); + private onMuteAudioClick = () => { + const call = this.getCallForRoom(); if (!call) { return; } @@ -1574,8 +1626,8 @@ export default class RoomView extends React.Component { this.forceUpdate(); // TODO: just update the voip buttons }; - onMuteVideoClick = () => { - const call = this._getCallForRoom(); + private onMuteVideoClick = () => { + const call = this.getCallForRoom(); if (!call) { return; } @@ -1584,14 +1636,14 @@ export default class RoomView extends React.Component { this.forceUpdate(); // TODO: just update the voip buttons }; - onStatusBarVisible = () => { + private onStatusBarVisible = () => { if (this.unmounted) return; this.setState({ statusBarVisible: true, }); }; - onStatusBarHidden = () => { + private onStatusBarHidden = () => { // This is currently not desired as it is annoying if it keeps expanding and collapsing if (this.unmounted) return; this.setState({ @@ -1604,12 +1656,12 @@ export default class RoomView extends React.Component { * * We pass it down to the scroll panel. */ - handleScrollKey = ev => { + private handleScrollKey = ev => { let panel; - if (this._searchResultsPanel.current) { - panel = this._searchResultsPanel.current; - } else if (this._messagePanel) { - panel = this._messagePanel; + if (this.searchResultsPanel.current) { + panel = this.searchResultsPanel.current; + } else if (this.messagePanel) { + panel = this.messagePanel; } if (panel) { @@ -1620,7 +1672,7 @@ export default class RoomView extends React.Component { /** * get any current call for this room */ - _getCallForRoom() { + private getCallForRoom() { if (!this.state.room) { return null; } @@ -1629,47 +1681,34 @@ export default class RoomView extends React.Component { // this has to be a proper method rather than an unnamed function, // otherwise react calls it with null on each update. - _gatherTimelinePanelRef = r => { - this._messagePanel = r; + private gatherTimelinePanelRef = r => { + this.messagePanel = r; if (r) { - console.log("updateTint from RoomView._gatherTimelinePanelRef"); + console.log("updateTint from RoomView.gatherTimelinePanelRef"); this.updateTint(); } }; - _getOldRoom() { + private getOldRoom() { const createEvent = this.state.room.currentState.getStateEvents("m.room.create", ""); if (!createEvent || !createEvent.getContent()['predecessor']) return null; return this.context.getRoom(createEvent.getContent()['predecessor']['room_id']); } - _getHiddenHighlightCount() { - const oldRoom = this._getOldRoom(); + getHiddenHighlightCount() { + const oldRoom = this.getOldRoom(); if (!oldRoom) return 0; return oldRoom.getUnreadNotificationCount('highlight'); } - _onHiddenHighlightsClick = () => { - const oldRoom = this._getOldRoom(); + onHiddenHighlightsClick = () => { + const oldRoom = this.getOldRoom(); if (!oldRoom) return; dis.dispatch({action: "view_room", room_id: oldRoom.roomId}); }; render() { - const RoomHeader = sdk.getComponent('rooms.RoomHeader'); - const ForwardMessage = sdk.getComponent("rooms.ForwardMessage"); - const AuxPanel = sdk.getComponent("rooms.AuxPanel"); - const SearchBar = sdk.getComponent("rooms.SearchBar"); - const PinnedEventsPanel = sdk.getComponent("rooms.PinnedEventsPanel"); - const ScrollPanel = sdk.getComponent("structures.ScrollPanel"); - const TintableSvg = sdk.getComponent("elements.TintableSvg"); - const RoomPreviewBar = sdk.getComponent("rooms.RoomPreviewBar"); - const TimelinePanel = sdk.getComponent("structures.TimelinePanel"); - const RoomUpgradeWarningBar = sdk.getComponent("rooms.RoomUpgradeWarningBar"); - const RoomRecoveryReminder = sdk.getComponent("rooms.RoomRecoveryReminder"); - const ErrorBoundary = sdk.getComponent("elements.ErrorBoundary"); - if (!this.state.room) { const loading = !this.state.matrixClientIsReady || this.state.roomLoading || this.state.peekLoading; if (loading) { @@ -1690,11 +1729,11 @@ export default class RoomView extends React.Component {
    ); } else { - var inviterName = undefined; + let inviterName = undefined; if (this.props.oobData) { inviterName = this.props.oobData.inviterName; } - var invitedEmail = undefined; + let invitedEmail = undefined; if (this.props.thirdPartyInvite) { invitedEmail = this.props.thirdPartyInvite.invitedEmail; } @@ -1773,13 +1812,13 @@ export default class RoomView extends React.Component { // We have successfully loaded this room, and are not previewing. // Display the "normal" room view. - const call = this._getCallForRoom(); + const call = this.getCallForRoom(); let inCall = false; if (call && (this.state.callState !== 'ended' && this.state.callState !== 'ringing')) { inCall = true; } - const scrollheader_classes = classNames({ + const scrollheaderClasses = classNames({ mx_RoomView_scrollheader: true, }); @@ -1818,17 +1857,21 @@ export default class RoomView extends React.Component { this.context.getKeyBackupEnabled() === false ); - const hiddenHighlightCount = this._getHiddenHighlightCount(); + const hiddenHighlightCount = this.getHiddenHighlightCount(); let aux = null; let previewBar; let hideCancel = false; let forceHideRightPanel = false; - if (this.state.forwardingEvent !== null) { + if (this.state.forwardingEvent) { aux = ; } else if (this.state.searching) { hideCancel = true; // has own cancel - aux = ; + aux = ; } else if (showRoomUpgradeBar) { aux = ; hideCancel = true; @@ -1841,25 +1884,26 @@ export default class RoomView extends React.Component { } else if (myMembership !== "join") { // We do have a room object for this room, but we're not currently in it. // We may have a 3rd party invite to it. - var inviterName = undefined; + let inviterName = undefined; if (this.props.oobData) { inviterName = this.props.oobData.inviterName; } - var invitedEmail = undefined; + let invitedEmail = undefined; if (this.props.thirdPartyInvite) { invitedEmail = this.props.thirdPartyInvite.invitedEmail; } hideCancel = true; previewBar = ( - ); if (!this.state.canPeek) { @@ -1873,8 +1917,11 @@ export default class RoomView extends React.Component { } } else if (hiddenHighlightCount > 0) { aux = ( - + {_t( "You have %(count)s unread notifications in a prior version of this room.", {count: hiddenHighlightCount}, @@ -1916,7 +1963,7 @@ export default class RoomView extends React.Component { showApps={this.state.showApps} e2eStatus={this.state.e2eStatus} resizeNotifier={this.props.resizeNotifier} - permalinkCreator={this._getPermalinkCreatorForRoom(this.state.room)} + permalinkCreator={this.getPermalinkCreatorForRoom(this.state.room)} />; } @@ -1936,26 +1983,37 @@ export default class RoomView extends React.Component { if (call.type === "video") { zoomButton = (
    - +
    ); videoMuteButton =
    - +
    ; } const voiceMuteButton =
    - +
    ; // wrap the existing status bar into a 'callStatusBar' which adds more knobs. @@ -1976,16 +2034,18 @@ export default class RoomView extends React.Component { if (this.state.searchResults) { // show searching spinner if (this.state.searchResults.results === undefined) { - searchResultsPanel = (
    ); + searchResultsPanel = ( +
    + ); } else { searchResultsPanel = ( -
  • +
  • { this.getSearchResultTiles() } ); @@ -2011,7 +2071,7 @@ export default class RoomView extends React.Component { // console.info("ShowUrlPreview for %s is %s", this.state.room.roomId, this.state.showUrlPreview); const messagePanel = ( ); + topUnreadMessagesBar = ( + + ); } let jumpToBottom; // Do not show JumpToBottomButton if we have search results showing, it makes no sense @@ -2050,19 +2109,14 @@ export default class RoomView extends React.Component { onScrollToBottomClick={this.jumpToLiveTimeline} />); } - const statusBarAreaClass = classNames( - "mx_RoomView_statusArea", - { - "mx_RoomView_statusArea_expanded": isStatusAreaExpanded, - }, - ); - const fadableSectionClasses = classNames( - "mx_RoomView_body", "mx_fadable", - { - "mx_fadable_faded": this.props.disabled, - }, - ); + const statusBarAreaClass = classNames("mx_RoomView_statusArea", { + "mx_RoomView_statusArea_expanded": isStatusAreaExpanded, + }); + + const fadableSectionClasses = classNames("mx_RoomView_body", "mx_fadable", { + "mx_fadable_faded": this.props.disabled, + }); const showRightPanel = !forceHideRightPanel && this.state.room && this.state.showRightPanel; const rightPanel = showRightPanel @@ -2079,7 +2133,7 @@ export default class RoomView extends React.Component { return ( -
    +
    ({ + roomLoading: true, + peekLoading: false, + shouldPeek: true, + membersLoaded: false, + numUnreadMessages: 0, + draggingFile: false, + searching: false, + guestsCanJoin: false, + canPeek: false, + showApps: false, + isAlone: false, + isPeeking: false, + showingPinned: false, + showReadReceipts: true, + showRightPanel: true, + joining: false, + atEndOfLiveTimeline: true, + atEndOfLiveTimelineInit: false, + showTopUnreadMessagesBar: false, + statusBarVisible: false, + canReact: false, + canReply: false, + useIRCLayout: false, + matrixClientIsReady: false, +}); +RoomContext.displayName = "RoomContext"; +export default RoomContext; diff --git a/src/utils/ShieldUtils.ts b/src/utils/ShieldUtils.ts index 878ed3959c..5fe653fed0 100644 --- a/src/utils/ShieldUtils.ts +++ b/src/utils/ShieldUtils.ts @@ -18,7 +18,13 @@ interface Room { roomId: string; } -export async function shieldStatusForRoom(client: Client, room: Room): Promise { +export enum E2EStatus { + Warning = "warning", + Verified = "verified", + Normal = "normal" +} + +export async function shieldStatusForRoom(client: Client, room: Room): Promise { const members = (await room.getEncryptionTargetMembers()).map(({userId}) => userId); const inDMMap = !!DMRoomMap.shared().getUserIdForRoomId(room.roomId); @@ -33,7 +39,7 @@ export async function shieldStatusForRoom(client: Client, room: Room): Promise Date: Tue, 8 Sep 2020 12:59:05 +0300 Subject: [PATCH 116/286] Add OpenID token request flow to WidgetApi As per MSC1960. --- src/widgets/WidgetApi.ts | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/widgets/WidgetApi.ts b/src/widgets/WidgetApi.ts index 74351720e2..4099793500 100644 --- a/src/widgets/WidgetApi.ts +++ b/src/widgets/WidgetApi.ts @@ -65,6 +65,13 @@ export interface FromWidgetRequest extends WidgetRequest { response: any; } +export interface OpenIDCredentials { + accessToken: string; + tokenType: string; + matrixServerName: string; + expiresIn: number; +} + /** * Handles Element <--> Widget interactions for embedded/standalone widgets. * @@ -78,6 +85,8 @@ export class WidgetApi extends EventEmitter { private inFlightRequests: { [requestId: string]: (reply: FromWidgetRequest) => void } = {}; private readyPromise: Promise; private readyPromiseResolve: () => void; + private openIDCredentialsCallback: () => void; + public openIDCredentials: OpenIDCredentials; /** * Set this to true if your widget is expecting a ready message from the client. False otherwise (default). @@ -121,6 +130,10 @@ export class WidgetApi extends EventEmitter { // Acknowledge that we're shut down now this.replyToRequest(payload, {}); }); + } else if (payload.action === KnownWidgetActions.ReceiveOpenIDCredentials) { + // Save OpenID credentials + this.setOpenIDCredentials(payload); + this.replyToRequest(payload, {}); } else { console.warn(`[WidgetAPI] Got unexpected action: ${payload.action}`); } @@ -135,6 +148,32 @@ export class WidgetApi extends EventEmitter { }); } + public setOpenIDCredentials(value: WidgetRequest) { + const data = value.data; + if (data.state === 'allowed') { + this.openIDCredentials = { + accessToken: data.access_token, + tokenType: data.token_type, + matrixServerName: data.matrix_server_name, + expiresIn: data.expires_in, + } + } else if (data.state === 'blocked') { + this.openIDCredentials = null; + } + if (['allowed', 'blocked'].includes(data.state) && this.openIDCredentialsCallback) { + this.openIDCredentialsCallback() + } + } + + public requestOpenIDCredentials(credentialsResponseCallback: () => void) { + this.openIDCredentialsCallback = credentialsResponseCallback; + this.callAction( + KnownWidgetActions.GetOpenIDCredentials, + {}, + this.setOpenIDCredentials, + ); + } + public waitReady(): Promise { return this.readyPromise; } From 3af0d33e3b775f22217982667913a166f19175e1 Mon Sep 17 00:00:00 2001 From: Jason Robinson Date: Tue, 8 Sep 2020 13:00:00 +0300 Subject: [PATCH 117/286] Make a few fields readonly As they're only set in the constructor. --- src/widgets/WidgetApi.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/widgets/WidgetApi.ts b/src/widgets/WidgetApi.ts index 4099793500..672cbf2a56 100644 --- a/src/widgets/WidgetApi.ts +++ b/src/widgets/WidgetApi.ts @@ -81,9 +81,9 @@ export interface OpenIDCredentials { * the given promise resolves. */ export class WidgetApi extends EventEmitter { - private origin: string; + private readonly origin: string; private inFlightRequests: { [requestId: string]: (reply: FromWidgetRequest) => void } = {}; - private readyPromise: Promise; + private readonly readyPromise: Promise; private readyPromiseResolve: () => void; private openIDCredentialsCallback: () => void; public openIDCredentials: OpenIDCredentials; From 07f7ff6831f84ac23b94f2742581659ba3d918a9 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 11:01:36 +0100 Subject: [PATCH 118/286] Fix WatchManager global room watchers --- src/settings/WatchManager.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/settings/WatchManager.ts b/src/settings/WatchManager.ts index d51439459c..925a99f2dc 100644 --- a/src/settings/WatchManager.ts +++ b/src/settings/WatchManager.ts @@ -18,11 +18,10 @@ import { SettingLevel } from "./SettingLevel"; export type CallbackFn = (changedInRoomId: string, atLevel: SettingLevel, newValAtLevel: any) => void; -const IRRELEVANT_ROOM = Symbol("any room"); +const IRRELEVANT_ROOM = String(null); interface RoomWatcherMap { - // @ts-ignore - TS wants string-only keys but we know better - https://github.com/Microsoft/TypeScript/issues/1863 - [roomId: string | symbol]: CallbackFn[]; + [roomId: string]: CallbackFn[]; } /** @@ -69,7 +68,7 @@ export class WatchManager { 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], [])); + callbacks.push(...Object.values(roomWatchers).flat(1)); } else if (roomWatchers[IRRELEVANT_ROOM]) { callbacks.push(...roomWatchers[IRRELEVANT_ROOM]); } From b75e2aa299469aa43244e02d72329209a2dce866 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 7 Sep 2020 16:13:05 +0100 Subject: [PATCH 119/286] WidgetEchoStore improve update event to include roomId and widgetId --- src/stores/WidgetEchoStore.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/stores/WidgetEchoStore.js b/src/stores/WidgetEchoStore.js index a5e7b12da0..7dd093d45e 100644 --- a/src/stores/WidgetEchoStore.js +++ b/src/stores/WidgetEchoStore.js @@ -93,13 +93,13 @@ class WidgetEchoStore extends EventEmitter { if (this._roomWidgetEcho[roomId] === undefined) this._roomWidgetEcho[roomId] = {}; this._roomWidgetEcho[roomId][widgetId] = state; - this.emit('update'); + this.emit('update', roomId, widgetId); } removeRoomWidgetEcho(roomId, widgetId) { delete this._roomWidgetEcho[roomId][widgetId]; if (Object.keys(this._roomWidgetEcho[roomId]).length === 0) delete this._roomWidgetEcho[roomId]; - this.emit('update'); + this.emit('update', roomId, widgetId); } } From 3bc42d74700d908c926ea2900aee89e0fa2b7e10 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 11:05:33 +0100 Subject: [PATCH 120/286] delint --- src/components/structures/RoomView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 05494318b9..de911325e8 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -37,7 +37,7 @@ import * as sdk from '../../index'; import CallHandler from '../../CallHandler'; import dis from '../../dispatcher/dispatcher'; import Tinter from '../../Tinter'; -import rate_limited_func from '../../ratelimitedfunc'; +import rateLimitedFunc from '../../ratelimitedfunc'; import * as ObjectUtils from '../../ObjectUtils'; import * as Rooms from '../../Rooms'; import eventSearch, {searchPagination} from '../../Searching'; @@ -1014,7 +1014,7 @@ export default class RoomView extends React.Component { } // rate limited because a power level change will emit an event for every member in the room. - private updateRoomMembers = rate_limited_func((dueToMember) => { + private updateRoomMembers = rateLimitedFunc((dueToMember) => { // a member state changed in this room // refresh the conf call notification state this.updateConfCallNotification(); From 48d9c94c88e135aa92fbf0a1ac20211bd1ad1160 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 7 Sep 2020 16:17:25 +0100 Subject: [PATCH 121/286] Extract editWidget into WidgetUtils --- src/components/views/elements/AppTile.js | 15 +-------------- src/utils/WidgetUtils.js | 10 ++++++++++ 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 01366091fd..3cdc1cab43 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -310,20 +310,7 @@ export default class AppTile extends React.Component { if (this.props.onEditClick) { this.props.onEditClick(); } else { - // TODO: Open the right manager for the widget - if (SettingsStore.getValue("feature_many_integration_managers")) { - IntegrationManagers.sharedInstance().openAll( - this.props.room, - 'type_' + this.props.app.type, - this.props.app.id, - ); - } else { - IntegrationManagers.sharedInstance().getPrimaryManager().open( - this.props.room, - 'type_' + this.props.app.type, - this.props.app.id, - ); - } + WidgetUtils.editWidget(this.props.room, this.props.app); } } diff --git a/src/utils/WidgetUtils.js b/src/utils/WidgetUtils.js index 645953210d..75ee7c7d88 100644 --- a/src/utils/WidgetUtils.js +++ b/src/utils/WidgetUtils.js @@ -405,6 +405,7 @@ export default class WidgetUtils { app.creatorUserId = senderUserId; app.id = appId; + app.roomId = roomId; app.eventId = eventId; app.name = app.name || app.type; @@ -471,4 +472,13 @@ export default class WidgetUtils { const url = new URL("jitsi.html#" + queryString, baseUrl); // this strips hash fragment from baseUrl return url.href; } + + static editWidget(room, app) { + // TODO: Open the right manager for the widget + if (SettingsStore.getValue("feature_many_integration_managers")) { + IntegrationManagers.sharedInstance().openAll(room, 'type_' + app.type, app.id); + } else { + IntegrationManagers.sharedInstance().getPrimaryManager().open(room, 'type_' + app.type, app.id); + } + } } From 99cd2dceecad7368e04818f6983f3f64fe94502e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 12:07:24 +0100 Subject: [PATCH 122/286] Fix create-react-class regression. Can't call setState in c'tor --- src/components/views/elements/PowerSelector.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/views/elements/PowerSelector.js b/src/components/views/elements/PowerSelector.js index e5f217dd90..66922df0f8 100644 --- a/src/components/views/elements/PowerSelector.js +++ b/src/components/views/elements/PowerSelector.js @@ -57,11 +57,14 @@ export default class PowerSelector extends React.Component { customValue: this.props.value, selectValue: 0, }; - - this._initStateFromProps(this.props); } // TODO: [REACT-WARNING] Replace with appropriate lifecycle event + // eslint-disable-next-line camelcase + UNSAFE_componentWillMount() { + this._initStateFromProps(this.props); + } + // eslint-disable-next-line camelcase UNSAFE_componentWillReceiveProps(newProps) { this._initStateFromProps(newProps); From abaf470261af94e40e29126b989a34a5b1697fc3 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Tue, 8 Sep 2020 09:04:47 +0000 Subject: [PATCH 123/286] Translated using Weblate (Swedish) Currently translated at 100.0% (2356 of 2356 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 03a6bc1e2e..2032eca4ff 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -615,7 +615,7 @@ "%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s satte framtida rumshistorik till okänd synlighet (%(visibility)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.": "Där denna sida innehåller identifierbar information, till exempel ett rums-, användar- eller grupp-ID, tas datan bort innan den skickas till servern.", "The remote side failed to pick up": "Mottagaren svarade inte", - "Jump to read receipt": "Hoppa till läsindikation", + "Jump to read receipt": "Hoppa till läskvitto", "This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.": "Denna process låter dig exportera nycklarna för meddelanden som du har fått i krypterade rum till en lokal fil. Du kommer sedan att kunna importera filen i en annan Matrix-klient i framtiden, så att den klienten också kan avkryptera meddelandena.", "Unknown for %(duration)s": "Okänt i %(duration)s", "Unknown": "Okänt", @@ -929,7 +929,7 @@ "Show join/leave messages (invites/kicks/bans unaffected)": "Visa \"gå med\"/lämna-meddelanden (inbjudningar/kickningar/banningar opåverkat)", "Show avatar changes": "Visa avatarändringar", "Show display name changes": "Visa visningsnamnsändringar", - "Show read receipts sent by other users": "Visa läsindikationer som skickats av andra användare", + "Show read receipts sent by other users": "Visa läskvitton som skickats av andra användare", "Show avatars in user and room mentions": "Visa avatarer i användar- och rumsbenämningar", "Enable big emoji in chat": "Aktivera stora emojier i chatt", "Send typing notifications": "Skicka \"skriver\"-statusar", @@ -1237,7 +1237,7 @@ "Multiple integration managers": "Flera integrationshanterare", "Show hidden events in timeline": "Visa dolda händelser i tidslinjen", "Low bandwidth mode": "Läge för låg bandbredd", - "Send read receipts for messages (requires compatible homeserver to disable)": "Skicka läsindikationer för meddelanden (kräver kompatibel hemserver för att inaktivera)", + "Send read receipts for messages (requires compatible homeserver to disable)": "Skicka läskvitton för meddelanden (kräver kompatibel hemserver för att inaktivera)", "When rooms are upgraded": "När rum uppgraderas", "Accept to continue:": "Acceptera för att fortsätta:", "ID": "ID", @@ -1522,12 +1522,12 @@ "Space": "Mellanslag", "End": "End", "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.": "Du har blivit utloggad från alla dina sessioner och kommer inte längre att motta pushnotiser. För att återaktivera pushnotiser, logga in igen på varje enhet.", - "Use Single Sign On to continue": "Använd single sign-on för att fortsätta", - "Confirm adding this email address by using Single Sign On to prove your identity.": "Bekräfta tilläggning av e-postadressen genom att använda single sign-on för att bevisa din identitet.", - "Single Sign On": "Single sign-on", + "Use Single Sign On to continue": "Använd externt konto för att fortsätta", + "Confirm adding this email address by using Single Sign On to prove your identity.": "Bekräfta tilläggning av e-postadressen genom att använda externt konto för att bevisa din identitet.", + "Single Sign On": "Externt konto", "Confirm adding email": "Bekräfta tilläggning av e-postadressen", "Click the button below to confirm adding this email address.": "Klicka på knappen nedan för att bekräfta tilläggning av e-postadressen.", - "Confirm adding this phone number by using Single Sign On to prove your identity.": "Bekräfta tilläggning av telefonnumret genom att använda single sign-on för att bevisa din identitet.", + "Confirm adding this phone number by using Single Sign On to prove your identity.": "Bekräfta tilläggning av telefonnumret genom att använda externt konto för att bevisa din identitet.", "Confirm adding phone number": "Bekräfta tilläggning av telefonnumret", "Click the button below to confirm adding this phone number.": "Klicka på knappen nedan för att bekräfta tilläggning av telefonnumret.", "Are you sure you want to cancel entering passphrase?": "Är du säker på att du vill avbryta inmatning av lösenfrasen?", @@ -1721,8 +1721,8 @@ "exists": "existerar", "Your homeserver does not support session management.": "Din hemservers stöder inte sessionshantering.", "Unable to load session list": "Kunde inte ladda sessionslistan", - "Confirm deleting these sessions by using Single Sign On to prove your identity.|other": "Bekräfta radering av dessa sessioner genom att använda single sign-on för att bekräfta din identitet.", - "Confirm deleting these sessions by using Single Sign On to prove your identity.|one": "Bekräfta radering av denna session genom att använda single sign-on för att bekräfta din identitet.", + "Confirm deleting these sessions by using Single Sign On to prove your identity.|other": "Bekräfta radering av dessa sessioner genom att använda externt konto för att bekräfta din identitet.", + "Confirm deleting these sessions by using Single Sign On to prove your identity.|one": "Bekräfta radering av denna session genom att använda externt konto för att bekräfta din identitet.", "Confirm deleting these sessions": "Bekräfta radering av dessa sessioner", "Click the button below to confirm deleting these sessions.|other": "Klicka på knappen nedan för att bekräfta radering av dessa sessioner.", "Click the button below to confirm deleting these sessions.|one": "Klicka på knappen nedan för att bekräfta radering av denna session.", @@ -2012,7 +2012,7 @@ "Please provide a room address": "Vänligen välj en rumsadress", "This address is available to use": "Adressen är tillgänglig", "This address is already in use": "Adressen är upptagen", - "Sign in with single sign-on": "Logga in med single sign-on", + "Sign in with single sign-on": "Logga in med externt konto", "Enter a server name": "Ange ett servernamn", "Looks good": "Ser bra ut", "Can't find this server or its room list": "Kan inte hitta den här servern eller dess rumslista", @@ -2072,7 +2072,7 @@ "You've previously used a newer version of %(brand)s with this session. To use this version again with end to end encryption, you will need to sign out and back in again.": "Du har tidigare använt en nyare version av %(brand)s med den här sessionen. Om du vill använda den här versionen igen med totalsträckskryptering behöver du logga ut och logga in igen.", "Incompatible Database": "Inkompatibel databas", "Continue With Encryption Disabled": "Fortsätt med kryptering inaktiverad", - "Confirm your account deactivation by using Single Sign On to prove your identity.": "Bekräfta din kontoinaktivering genom att använda single sign-on för att bevisa din identitet.", + "Confirm your account deactivation by using Single Sign On to prove your identity.": "Bekräfta din kontoinaktivering genom att använda externt konto för att bevisa din identitet.", "Are you sure you want to deactivate your account? This is irreversible.": "Är du säker på att du vill inaktivera ditt konto? Detta är oåterkalleligt.", "Confirm account deactivation": "Bekräfta kontoinaktivering", "Security & privacy": "Säkerhet & sekretess", @@ -2084,7 +2084,7 @@ "Verifying this user will mark their session as trusted, and also mark your session as trusted to them.": "Att verifiera den här användaren kommer att markera dess session som betrodd, och markera din session som betrodd för denne.", "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.": "Verifiera denna enhet för att markera den som betrodd. Att lita på denna enhet och andra användare ger en extra sinnesfrid när man använder totalsträckskrypterade meddelanden.", "Verifying this device will mark it as trusted, and users who have verified with you will trust this device.": "Att verifiera den här enheten kommer att markera den som betrodd, användare som har verifierat dig kommer att lita på den här enheten.", - "To continue, use Single Sign On to prove your identity.": "För att fortsätta, använd single sign-on för att bevisa din identitet.", + "To continue, use Single Sign On to prove your identity.": "För att fortsätta, använd externt konto för att bevisa din identitet.", "Click the button below to confirm your identity.": "Klicka på knappen nedan för att bekräfta din identitet.", "Failed to invite the following users to chat: %(csvUsers)s": "Misslyckades att bjuda in följande användare till chatten: %(csvUsers)s", "We couldn't create your DM. Please check the users you want to invite and try again.": "Vi kunde inte skapa ditt DM. Vänligen kolla användarna du försöker bjuda in och försök igen.", From 5c3c8cfb068a569bd66cb643eb0b28ff6fb00b0d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 15:07:16 +0100 Subject: [PATCH 124/286] Make Travis an ounce happier --- src/settings/WatchManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/WatchManager.ts b/src/settings/WatchManager.ts index 925a99f2dc..ea2f158ef6 100644 --- a/src/settings/WatchManager.ts +++ b/src/settings/WatchManager.ts @@ -18,7 +18,7 @@ import { SettingLevel } from "./SettingLevel"; export type CallbackFn = (changedInRoomId: string, atLevel: SettingLevel, newValAtLevel: any) => void; -const IRRELEVANT_ROOM = String(null); +const IRRELEVANT_ROOM: string = null; interface RoomWatcherMap { [roomId: string]: CallbackFn[]; From 1b99c11b1189fdf529471ede9c0f1482f10921fd Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 15:09:45 +0100 Subject: [PATCH 125/286] tidy --- src/utils/permalinks/Permalinks.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/utils/permalinks/Permalinks.js b/src/utils/permalinks/Permalinks.js index d97ba55c4d..3e510ffee9 100644 --- a/src/utils/permalinks/Permalinks.js +++ b/src/utils/permalinks/Permalinks.js @@ -333,8 +333,7 @@ export function tryTransformPermalinkToLocalHref(permalink: string): string { const eventIdPart = permalinkParts.eventId ? `/${permalinkParts.eventId}` : ''; permalink = `#/room/${permalinkParts.roomIdOrAlias}${eventIdPart}`; if (permalinkParts.viaServers.length > 0) { - const riotPermalinkConstructor = new SpecPermalinkConstructor(); - permalink += riotPermalinkConstructor.encodeServerCandidates(permalinkParts.viaServers); + permalink += new SpecPermalinkConstructor().encodeServerCandidates(permalinkParts.viaServers); } } else if (permalinkParts.groupId) { permalink = `#/group/${permalinkParts.groupId}`; From 9d85d0436c4be60c5ef3ddc8ee680ca3ee41da36 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 15:22:23 +0100 Subject: [PATCH 126/286] iterate PR --- src/components/structures/RoomView.tsx | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index de911325e8..3395a6011c 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -69,6 +69,8 @@ import PinnedEventsPanel from "../views/rooms/PinnedEventsPanel"; import AuxPanel from "../views/rooms/AuxPanel"; import RoomHeader from "../views/rooms/RoomHeader"; import TintableSvg from "../views/elements/TintableSvg"; +import type * as ConferenceHandler from '../../VectorConferenceHandler'; +import {XOR} from "../../@types/common"; const DEBUG = false; let debuglog = function(msg: string) {}; @@ -81,7 +83,7 @@ if (DEBUG) { } interface IProps { - ConferenceHandler?: any; + ConferenceHandler?: ConferenceHandler; // An object representing a third party invite to join this room // Fields: @@ -102,7 +104,11 @@ interface IProps { // * avatarUrl (string) The mxc:// avatar URL for the room // * inviterName (string) The display name of the person who // * invited us to the room - oobData?: any; + oobData?: { + name?: string; + avatarUrl?: string; + inviterName?: string; + }; // Servers the RoomView can use to try and assist joins viaServers?: string[]; @@ -137,7 +143,12 @@ export interface IState { searching: boolean; searchTerm?: string; searchScope?: "All" | "Room"; - searchResults?: any; + searchResults?: XOR<{}, { + count: number; + highlights: string[]; + results: MatrixEvent[]; + next_batch: string; // eslint-disable-line camelcase + }>; searchHighlights?: string[]; searchInProgress?: boolean; callState?: string; @@ -166,7 +177,11 @@ export interface IState { statusBarVisible: boolean; // We load this later by asking the js-sdk to suggest a version for us. // This object is the result of Room#getRecommendedVersion() - upgradeRecommendation?: any; + upgradeRecommendation?: { + version: string; + needsUpgrade: boolean; + urgent: boolean; + }; canReact: boolean; canReply: boolean; useIRCLayout: boolean; @@ -1034,7 +1049,7 @@ export default class RoomView extends React.Component { private checkIfAlone(room: Room, countInfluence?: number) { let warnedAboutLonelyRoom = false; if (localStorage) { - warnedAboutLonelyRoom = !!localStorage.getItem('mx_user_alone_warned_' + this.state.room.roomId); + warnedAboutLonelyRoom = Boolean(localStorage.getItem('mx_user_alone_warned_' + this.state.room.roomId)); } if (warnedAboutLonelyRoom) { if (this.state.isAlone) this.setState({isAlone: false}); From 2fbb551035efa159bedbbd52211a25e3da9604ab Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 4 Sep 2020 14:09:34 +0100 Subject: [PATCH 127/286] Put backup details in a table --- .../views/settings/_SecureBackupPanel.scss | 12 +++++++++ .../views/settings/SecureBackupPanel.js | 26 +++++++++++++++---- src/i18n/strings/en_EN.json | 6 ++--- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/res/css/views/settings/_SecureBackupPanel.scss b/res/css/views/settings/_SecureBackupPanel.scss index 548e72fbc3..587cab8f36 100644 --- a/res/css/views/settings/_SecureBackupPanel.scss +++ b/res/css/views/settings/_SecureBackupPanel.scss @@ -35,3 +35,15 @@ limitations under the License. .mx_SecureBackupPanel_buttonRow { margin: 1em 0; } + +.mx_SecureBackupPanel_statusList { + border-spacing: 0; + + td { + padding: 0; + + &:first-of-type { + padding-inline-end: 1em; + } + } +} diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js index 683120f5b6..7f7a014df9 100644 --- a/src/components/views/settings/SecureBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -174,6 +174,7 @@ export default class SecureBackupPanel extends React.PureComponent { } = this.state; let statusDescription; + let extraDetailsTableRows; let extraDetails; let actions; if (error) { @@ -316,9 +317,18 @@ export default class SecureBackupPanel extends React.PureComponent { ; } + extraDetailsTableRows = <> + + {_t("Backup version:")} + {backupInfo.version} + + + {_t("Algorithm:")} + {backupInfo.algorithm} + + ; + extraDetails = <> -
    {_t("Backup version: ")}{backupInfo.version}
    -
    {_t("Algorithm: ")}{backupInfo.algorithm}
    {uploadStatus}
    {backupSigStatuses}
    {trustedLocally}
    @@ -359,9 +369,15 @@ export default class SecureBackupPanel extends React.PureComponent { {statusDescription}
    {_t("Advanced")} -
    {_t("Backup key stored: ")}{ - backupKeyStored === true ? _t("in secret storage") : _t("not stored") - }
    + + + + + + {extraDetailsTableRows} +
    {_t("Backup key stored:")}{ + backupKeyStored === true ? _t("in secret storage") : _t("not stored") + }
    {extraDetails}
    {actions} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 49e8b1d2e8..476ce11b2d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -750,13 +750,13 @@ "Backup has an invalid signature from unverified session ": "Backup has an invalid signature from unverified session ", "Backup is not signed by any of your sessions": "Backup is not signed by any of your sessions", "This backup is trusted because it has been restored on this session": "This backup is trusted because it has been restored on this session", - "Backup version: ": "Backup version: ", - "Algorithm: ": "Algorithm: ", + "Backup version:": "Backup version:", + "Algorithm:": "Algorithm:", "Your keys are not being backed up from this session.": "Your keys are not being backed up from this session.", "Back up your keys before signing out to avoid losing them.": "Back up your keys before signing out to avoid losing them.", "Start using Key Backup": "Start using Key Backup", "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.": "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.", - "Backup key stored: ": "Backup key stored: ", + "Backup key stored:": "Backup key stored:", "not stored": "not stored", "Identity Server URL must be HTTPS": "Identity Server URL must be HTTPS", "Not a valid Identity Server (status code %(code)s)": "Not a valid Identity Server (status code %(code)s)", From aae68f7d1aa3fecb68a590a420de062ee695308f Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 8 Sep 2020 14:10:34 +0100 Subject: [PATCH 128/286] Move 4S status to backup panel This moves the various 4S status diagnostics to the backup panel and out of the cross-signing panel. The available actions are unchanged as part of this commit, but they will be updated next. --- .../views/settings/CrossSigningPanel.js | 61 +++------------ .../views/settings/SecureBackupPanel.js | 78 +++++++++++++++---- src/i18n/strings/en_EN.json | 18 +++-- 3 files changed, 84 insertions(+), 73 deletions(-) diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index 3eeb072e2d..8ef68e4b2a 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -31,13 +31,13 @@ export default class CrossSigningPanel extends React.PureComponent { this.state = { error: null, - crossSigningPublicKeysOnDevice: false, - crossSigningPrivateKeysInStorage: false, - masterPrivateKeyCached: false, - selfSigningPrivateKeyCached: false, - userSigningPrivateKeyCached: false, - sessionBackupKeyCached: false, - secretStorageKeyInAccount: false, + crossSigningPublicKeysOnDevice: null, + crossSigningPrivateKeysInStorage: null, + masterPrivateKeyCached: null, + selfSigningPrivateKeyCached: null, + userSigningPrivateKeyCached: null, + homeserverSupportsCrossSigning: null, + crossSigningReady: null, }; } @@ -83,14 +83,9 @@ export default class CrossSigningPanel extends React.PureComponent { const masterPrivateKeyCached = !!(pkCache && await pkCache.getCrossSigningKeyCache("master")); const selfSigningPrivateKeyCached = !!(pkCache && await pkCache.getCrossSigningKeyCache("self_signing")); const userSigningPrivateKeyCached = !!(pkCache && await pkCache.getCrossSigningKeyCache("user_signing")); - const sessionBackupKeyFromCache = await cli._crypto.getSessionBackupPrivateKey(); - const sessionBackupKeyCached = !!(sessionBackupKeyFromCache); - const sessionBackupKeyWellFormed = sessionBackupKeyFromCache instanceof Uint8Array; - const secretStorageKeyInAccount = await secretStorage.hasKey(); const homeserverSupportsCrossSigning = await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing"); const crossSigningReady = await cli.isCrossSigningReady(); - const secretStorageReady = await cli.isSecretStorageReady(); this.setState({ crossSigningPublicKeysOnDevice, @@ -98,12 +93,8 @@ export default class CrossSigningPanel extends React.PureComponent { masterPrivateKeyCached, selfSigningPrivateKeyCached, userSigningPrivateKeyCached, - sessionBackupKeyCached, - sessionBackupKeyWellFormed, - secretStorageKeyInAccount, homeserverSupportsCrossSigning, crossSigningReady, - secretStorageReady, }); } @@ -149,12 +140,8 @@ export default class CrossSigningPanel extends React.PureComponent { masterPrivateKeyCached, selfSigningPrivateKeyCached, userSigningPrivateKeyCached, - sessionBackupKeyCached, - sessionBackupKeyWellFormed, - secretStorageKeyInAccount, homeserverSupportsCrossSigning, crossSigningReady, - secretStorageReady, } = this.state; let errorSection; @@ -169,14 +156,9 @@ export default class CrossSigningPanel extends React.PureComponent { summarisedStatus =

    {_t( "Your homeserver does not support cross-signing.", )}

    ; - } else if (crossSigningReady && secretStorageReady) { + } else if (crossSigningReady) { summarisedStatus =

    ✅ {_t( - "Cross-signing and secret storage are ready for use.", - )}

    ; - } else if (crossSigningReady && !secretStorageReady) { - summarisedStatus =

    ✅ {_t( - "Cross-signing is ready for use, but secret storage is " + - "currently not being used to backup your keys.", + "Cross-signing is ready for use.", )}

    ; } else if (crossSigningPrivateKeysInStorage) { summarisedStatus =

    {_t( @@ -185,17 +167,15 @@ export default class CrossSigningPanel extends React.PureComponent { )}

    ; } else { summarisedStatus =

    {_t( - "Cross-signing and secret storage are not yet set up.", + "Cross-signing is not set up.", )}

    ; } const keysExistAnywhere = ( - secretStorageKeyInAccount || crossSigningPrivateKeysInStorage || crossSigningPublicKeysOnDevice ); const keysExistEverywhere = ( - secretStorageKeyInAccount && crossSigningPrivateKeysInStorage && crossSigningPublicKeysOnDevice ); @@ -223,16 +203,6 @@ export default class CrossSigningPanel extends React.PureComponent { ); } - let sessionBackupKeyWellFormedText = ""; - if (sessionBackupKeyCached) { - sessionBackupKeyWellFormedText = ", "; - if (sessionBackupKeyWellFormed) { - sessionBackupKeyWellFormedText += _t("well formed"); - } else { - sessionBackupKeyWellFormedText += _t("unexpected type"); - } - } - return (
    {summarisedStatus} @@ -259,17 +229,6 @@ export default class CrossSigningPanel extends React.PureComponent { {_t("User signing private key:")} {userSigningPrivateKeyCached ? _t("cached locally") : _t("not found locally")} - - {_t("Session backup key:")} - - {sessionBackupKeyCached ? _t("cached locally") : _t("not found locally")} - {sessionBackupKeyWellFormedText} - - - - {_t("Secret storage public key:")} - {secretStorageKeyInAccount ? _t("in account data") : _t("not found")} - {_t("Homeserver feature support:")} {homeserverSupportsCrossSigning ? _t("exists") : _t("not found")} diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js index 7f7a014df9..0f43770288 100644 --- a/src/components/views/settings/SecureBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -34,9 +34,13 @@ export default class SecureBackupPanel extends React.PureComponent { this.state = { loading: true, error: null, + backupKeyStored: null, + backupKeyCached: null, + backupKeyWellFormed: null, + secretStorageKeyInAccount: null, + secretStorageReady: null, backupInfo: null, backupSigStatus: null, - backupKeyStored: null, sessionsRemaining: 0, }; } @@ -76,56 +80,73 @@ export default class SecureBackupPanel extends React.PureComponent { } async _checkKeyBackupStatus() { + this._getUpdatedDiagnostics(); try { const {backupInfo, trustInfo} = await MatrixClientPeg.get().checkKeyBackup(); - const backupKeyStored = Boolean(await MatrixClientPeg.get().isKeyBackupKeyStored()); this.setState({ + loading: false, + error: null, backupInfo, backupSigStatus: trustInfo, - backupKeyStored, - error: null, - loading: false, }); } catch (e) { console.log("Unable to fetch check backup status", e); if (this._unmounted) return; this.setState({ + loading: false, error: e, backupInfo: null, backupSigStatus: null, - backupKeyStored: null, - loading: false, }); } } async _loadBackupStatus() { - this.setState({loading: true}); + this.setState({ loading: true }); + this._getUpdatedDiagnostics(); try { const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion(); const backupSigStatus = await MatrixClientPeg.get().isKeyBackupTrusted(backupInfo); - const backupKeyStored = await MatrixClientPeg.get().isKeyBackupKeyStored(); if (this._unmounted) return; this.setState({ + loading: false, error: null, backupInfo, backupSigStatus, - backupKeyStored, - loading: false, }); } catch (e) { console.log("Unable to fetch key backup status", e); if (this._unmounted) return; this.setState({ + loading: false, error: e, backupInfo: null, backupSigStatus: null, - backupKeyStored: null, - loading: false, }); } } + async _getUpdatedDiagnostics() { + const cli = MatrixClientPeg.get(); + const secretStorage = cli._crypto._secretStorage; + + const backupKeyStored = await cli.isKeyBackupKeyStored(); + const backupKeyFromCache = await cli._crypto.getSessionBackupPrivateKey(); + const backupKeyCached = !!(backupKeyFromCache); + const backupKeyWellFormed = backupKeyFromCache instanceof Uint8Array; + const secretStorageKeyInAccount = await secretStorage.hasKey(); + const secretStorageReady = await cli.isSecretStorageReady(); + + if (this._unmounted) return; + this.setState({ + backupKeyStored, + backupKeyCached, + backupKeyWellFormed, + secretStorageKeyInAccount, + secretStorageReady, + }); + } + _startNewBackup = () => { Modal.createTrackedDialogAsync('Key Backup', 'Key Backup', import('../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog'), @@ -167,9 +188,13 @@ export default class SecureBackupPanel extends React.PureComponent { const { loading, error, + backupKeyStored, + backupKeyCached, + backupKeyWellFormed, + secretStorageKeyInAccount, + secretStorageReady, backupInfo, backupSigStatus, - backupKeyStored, sessionsRemaining, } = this.state; @@ -359,6 +384,16 @@ export default class SecureBackupPanel extends React.PureComponent { ); } + let backupKeyWellFormedText = ""; + if (backupKeyCached) { + backupKeyWellFormedText = ", "; + if (backupKeyWellFormed) { + backupKeyWellFormedText += _t("well formed"); + } else { + backupKeyWellFormedText += _t("unexpected type"); + } + } + return (

    {_t( @@ -376,6 +411,21 @@ export default class SecureBackupPanel extends React.PureComponent { backupKeyStored === true ? _t("in secret storage") : _t("not stored") } + + {_t("Backup key cached:")} + + {backupKeyCached ? _t("cached locally") : _t("not found locally")} + {backupKeyWellFormedText} + + + + {_t("Secret storage public key:")} + {secretStorageKeyInAccount ? _t("in account data") : _t("not found")} + + + {_t("Secret storage:")} + {secretStorageReady ? _t("ready") : _t("not ready")} + {extraDetailsTableRows} {extraDetails} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 476ce11b2d..1bf431f6e0 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -645,14 +645,11 @@ "Confirm password": "Confirm password", "Change Password": "Change Password", "Your homeserver does not support cross-signing.": "Your homeserver does not support cross-signing.", - "Cross-signing and secret storage are ready for use.": "Cross-signing and secret storage are ready for use.", - "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.", + "Cross-signing is ready for use.": "Cross-signing is ready for use.", "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.": "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.", - "Cross-signing and secret storage are not yet set up.": "Cross-signing and secret storage are not yet set up.", + "Cross-signing is not set up.": "Cross-signing is not set up.", "Reset cross-signing and secret storage": "Reset cross-signing and secret storage", "Bootstrap cross-signing and secret storage": "Bootstrap cross-signing and secret storage", - "well formed": "well formed", - "unexpected type": "unexpected type", "Cross-signing public keys:": "Cross-signing public keys:", "in memory": "in memory", "not found": "not found", @@ -663,9 +660,6 @@ "not found locally": "not found locally", "Self signing private key:": "Self signing private key:", "User signing private key:": "User signing private key:", - "Session backup key:": "Session backup key:", - "Secret storage public key:": "Secret storage public key:", - "in account data": "in account data", "Homeserver feature support:": "Homeserver feature support:", "exists": "exists", "Your homeserver does not support session management.": "Your homeserver does not support session management.", @@ -755,9 +749,17 @@ "Your keys are not being backed up from this session.": "Your keys are not being backed up from this session.", "Back up your keys before signing out to avoid losing them.": "Back up your keys before signing out to avoid losing them.", "Start using Key Backup": "Start using Key Backup", + "well formed": "well formed", + "unexpected type": "unexpected type", "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.": "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.", "Backup key stored:": "Backup key stored:", "not stored": "not stored", + "Backup key cached:": "Backup key cached:", + "Secret storage public key:": "Secret storage public key:", + "in account data": "in account data", + "Secret storage:": "Secret storage:", + "ready": "ready", + "not ready": "not ready", "Identity Server URL must be HTTPS": "Identity Server URL must be HTTPS", "Not a valid Identity Server (status code %(code)s)": "Not a valid Identity Server (status code %(code)s)", "Could not connect to Identity Server": "Could not connect to Identity Server", From 11e349d6c8cc8416dc329bc3d077cf0d12e6eb91 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 28 Aug 2020 10:46:01 +0100 Subject: [PATCH 129/286] Update e2e iconography --- res/img/e2e/disabled.svg | 5 +++++ res/img/e2e/normal.svg | 4 ++-- res/img/e2e/verified.svg | 4 ++-- res/img/e2e/warning.svg | 4 ++-- 4 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 res/img/e2e/disabled.svg diff --git a/res/img/e2e/disabled.svg b/res/img/e2e/disabled.svg new file mode 100644 index 0000000000..2f6110a36a --- /dev/null +++ b/res/img/e2e/disabled.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/e2e/normal.svg b/res/img/e2e/normal.svg index 23ca78e44d..83b544a326 100644 --- a/res/img/e2e/normal.svg +++ b/res/img/e2e/normal.svg @@ -1,3 +1,3 @@ - - + + diff --git a/res/img/e2e/verified.svg b/res/img/e2e/verified.svg index ac4827baed..f90d9db554 100644 --- a/res/img/e2e/verified.svg +++ b/res/img/e2e/verified.svg @@ -1,3 +1,3 @@ - - + + diff --git a/res/img/e2e/warning.svg b/res/img/e2e/warning.svg index d42922892a..58f5c3b7d1 100644 --- a/res/img/e2e/warning.svg +++ b/res/img/e2e/warning.svg @@ -1,3 +1,3 @@ - - + + From 25273442949ab1a6099b4ba5a574ba413959b8b8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 28 Aug 2020 10:50:27 +0100 Subject: [PATCH 130/286] Create name/title Widget utils --- src/components/views/elements/PersistentApp.js | 2 +- src/components/views/rooms/AppsDrawer.js | 2 +- src/i18n/strings/en_EN.json | 1 + src/utils/WidgetUtils.js | 11 +++++++++++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/components/views/elements/PersistentApp.js b/src/components/views/elements/PersistentApp.js index bdf5f60234..686739a9f7 100644 --- a/src/components/views/elements/PersistentApp.js +++ b/src/components/views/elements/PersistentApp.js @@ -76,7 +76,7 @@ export default class PersistentApp extends React.Component { userId={MatrixClientPeg.get().credentials.userId} show={true} creatorUserId={app.creatorUserId} - widgetPageTitle={(app.data && app.data.title) ? app.data.title : ''} + widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)} waitForIframeLoad={app.waitForIframeLoad} whitelistCapabilities={capWhitelist} showDelete={false} diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index fca46b453f..1eca493e14 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -171,7 +171,7 @@ export default class AppsDrawer extends React.Component { userId={this.props.userId} show={this.props.showApps} creatorUserId={app.creatorUserId} - widgetPageTitle={(app.data && app.data.title) ? app.data.title : ''} + widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)} waitForIframeLoad={app.waitForIframeLoad} whitelistCapabilities={capWhitelist} />); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 47063bdae4..54a4bd6695 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -387,6 +387,7 @@ "Common names and surnames are easy to guess": "Common names and surnames are easy to guess", "Straight rows of keys are easy to guess": "Straight rows of keys are easy to guess", "Short keyboard patterns are easy to guess": "Short keyboard patterns are easy to guess", + "Unknown App": "Unknown App", "Help us improve %(brand)s": "Help us improve %(brand)s", "Send anonymous usage data which helps us improve %(brand)s. This will use a cookie.": "Send anonymous usage data which helps us improve %(brand)s. This will use a cookie.", "I want to help": "I want to help", diff --git a/src/utils/WidgetUtils.js b/src/utils/WidgetUtils.js index be176d042f..d4ed093a24 100644 --- a/src/utils/WidgetUtils.js +++ b/src/utils/WidgetUtils.js @@ -32,6 +32,7 @@ import {Capability} from "../widgets/WidgetApi"; import {Room} from "matrix-js-sdk/src/models/room"; import {WidgetType} from "../widgets/WidgetType"; import {objectClone} from "./objects"; +import {_t} from "../languageHandler"; export default class WidgetUtils { /* Returns true if user is able to send state events to modify widgets in this room @@ -486,4 +487,14 @@ export default class WidgetUtils { IntegrationManagers.sharedInstance().getPrimaryManager().open(room, 'type_' + app.type, app.id); } } + + static getWidgetName(app) { + if (!app || !app.name) return ""; + return app.name.trim() || _t("Unknown App"); + } + + static getWidgetDataTitle(app) { + if (!app || !app.data || !app.data.title) return ""; + return app.data.title.trim(); + } } From 8f94a42dafcf30cf4776c2a6802ce94275034830 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 28 Aug 2020 10:52:04 +0100 Subject: [PATCH 131/286] Update Right Panel phase recall behaviour --- src/components/structures/RightPanel.js | 6 +----- src/stores/RightPanelStore.ts | 10 ++++++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js index 11416b29fb..b2b1ceae00 100644 --- a/src/components/structures/RightPanel.js +++ b/src/components/structures/RightPanel.js @@ -47,10 +47,10 @@ export default class RightPanel extends React.Component { constructor(props, context) { super(props, context); this.state = { + ...RightPanelStore.getSharedInstance().roomPanelPhaseParams, phase: this._getPhaseFromProps(), isUserPrivilegedInGroup: null, member: this._getUserForPanel(), - verificationRequest: RightPanelStore.getSharedInstance().roomPanelPhaseParams.verificationRequest, }; this.onAction = this.onAction.bind(this); this.onRoomStateMember = this.onRoomStateMember.bind(this); @@ -102,10 +102,6 @@ export default class RightPanel extends React.Component { } return RightPanelPhases.RoomMemberInfo; } else { - if (!RIGHT_PANEL_PHASES_NO_ARGS.includes(rps.roomPanelPhase)) { - dis.dispatch({action: Action.SetRightPanelPhase, phase: RightPanelPhases.RoomMemberList}); - return RightPanelPhases.RoomMemberList; - } return rps.roomPanelPhase; } } diff --git a/src/stores/RightPanelStore.ts b/src/stores/RightPanelStore.ts index 34445d007b..c539fcdb40 100644 --- a/src/stores/RightPanelStore.ts +++ b/src/stores/RightPanelStore.ts @@ -33,6 +33,8 @@ interface RightPanelStoreState { lastRoomPhase: RightPanelPhases; lastGroupPhase: RightPanelPhases; + previousPhase?: RightPanelPhases; + // Extra information about the last phase lastRoomPhaseParams: {[key: string]: any}; } @@ -89,6 +91,10 @@ export default class RightPanelStore extends Store { return this.state.lastGroupPhase; } + get previousPhase(): RightPanelPhases | null { + return RIGHT_PANEL_PHASES_NO_ARGS.includes(this.state.previousPhase) ? this.state.previousPhase : null; + } + get visibleRoomPanelPhase(): RightPanelPhases { return this.isOpenForRoom ? this.roomPanelPhase : null; } @@ -176,23 +182,27 @@ export default class RightPanelStore extends Store { if (targetPhase === this.state.lastGroupPhase) { this.setState({ showGroupPanel: !this.state.showGroupPanel, + previousPhase: null, }); } else { this.setState({ lastGroupPhase: targetPhase, showGroupPanel: true, + previousPhase: this.state.lastGroupPhase, }); } } else { if (targetPhase === this.state.lastRoomPhase && !refireParams) { this.setState({ showRoomPanel: !this.state.showRoomPanel, + previousPhase: null, }); } else { this.setState({ lastRoomPhase: targetPhase, showRoomPanel: true, lastRoomPhaseParams: refireParams || {}, + previousPhase: this.state.lastRoomPhase, }); } } From 89a836100d62842c1af721749b83c0bfaa90d680 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 1 Sep 2020 14:32:21 +0100 Subject: [PATCH 132/286] small css tweaks to closer match the figma --- res/css/structures/_RightPanel.scss | 16 +++++++--------- res/css/structures/_UserMenu.scss | 3 +-- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/res/css/structures/_RightPanel.scss b/res/css/structures/_RightPanel.scss index c7c0d6fac4..7e5ab7cdbd 100644 --- a/res/css/structures/_RightPanel.scss +++ b/res/css/structures/_RightPanel.scss @@ -95,15 +95,7 @@ limitations under the License. mask-position: center; } -.mx_RightPanel_headerButton_highlight { - background: rgba($accent-color, 0.25); - // make the icon the accent color too - &::before { - background-color: $accent-color !important; - } -} - -.mx_RightPanel_headerButton:not(.mx_RightPanel_headerButton_highlight) { +.mx_RightPanel_headerButton { &:hover { background: rgba($accent-color, 0.1); @@ -113,6 +105,12 @@ limitations under the License. } } +.mx_RightPanel_headerButton_highlight { + &::before { + background-color: $accent-color !important; + } +} + .mx_RightPanel_headerButton_badge { font-size: $font-8px; border-radius: 8px; diff --git a/res/css/structures/_UserMenu.scss b/res/css/structures/_UserMenu.scss index 6fa2f2578e..fecac40e4e 100644 --- a/res/css/structures/_UserMenu.scss +++ b/res/css/structures/_UserMenu.scss @@ -15,7 +15,6 @@ limitations under the License. */ .mx_UserMenu { - // to make the menu button sort of aligned with the explore button below padding-right: 6px; @@ -59,7 +58,7 @@ limitations under the License. mask-position: center; mask-size: contain; mask-repeat: no-repeat; - background: $primary-fg-color; + background: $tertiary-fg-color; mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); } } From eb7f6f4c4b5036e1c0e46babb9a3e3fafeb65963 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 7 Sep 2020 16:19:42 +0100 Subject: [PATCH 133/286] Create new WidgetStore to track all widgets stuff --- src/@types/global.d.ts | 2 + src/stores/WidgetStore.ts | 191 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+) create mode 100644 src/stores/WidgetStore.ts diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index 1a361e7b55..de3eb5e767 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -29,6 +29,7 @@ import {ActiveRoomObserver} from "../ActiveRoomObserver"; import {Notifier} from "../Notifier"; import type {Renderer} from "react-dom"; import RightPanelStore from "../stores/RightPanelStore"; +import {WidgetStore} from "../stores/WidgetStore"; declare global { interface Window { @@ -51,6 +52,7 @@ declare global { mxSettingsStore: SettingsStore; mxNotifier: typeof Notifier; mxRightPanelStore: RightPanelStore; + mxWidgetStore: WidgetStore; } interface Document { diff --git a/src/stores/WidgetStore.ts b/src/stores/WidgetStore.ts new file mode 100644 index 0000000000..b31fc99515 --- /dev/null +++ b/src/stores/WidgetStore.ts @@ -0,0 +1,191 @@ +/* +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 { Room } from "matrix-js-sdk/src/models/room"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; + +import { ActionPayload } from "../dispatcher/payloads"; +import { AsyncStoreWithClient } from "./AsyncStoreWithClient"; +import defaultDispatcher from "../dispatcher/dispatcher"; +import SettingsStore from "../settings/SettingsStore"; +import WidgetEchoStore from "../stores/WidgetEchoStore"; +import WidgetUtils from "../utils/WidgetUtils"; +import {IRRELEVANT_ROOM} from "../settings/WatchManager"; +import {SettingLevel} from "../settings/SettingLevel"; + +interface IState {} + +export interface IApp { + id: string; + type: string; + roomId: string; + eventId: string; + creatorUserId: string; + waitForIframeLoad?: boolean; + // eslint-disable-next-line camelcase + avatar_url: string; // MSC2765 https://github.com/matrix-org/matrix-doc/pull/2765 +} + +interface IRoomWidgets { + widgets: IApp[]; + pinned: Set; +} + +// TODO consolidate WidgetEchoStore into this +// TODO consolidate ActiveWidgetStore into this +export class WidgetStore extends AsyncStoreWithClient { + private static internalInstance = new WidgetStore(); + + private widgetMap = new Map(); + private roomMap = new Map(); + + private constructor() { + super(defaultDispatcher, {}); + + SettingsStore.watchSetting("Widgets.pinned", IRRELEVANT_ROOM, this.onPinnedWidgetsChange); + WidgetEchoStore.on("update", this.onWidgetEchoStoreUpdate); + } + + public static get instance(): WidgetStore { + return WidgetStore.internalInstance; + } + + private initRoom(roomId: string) { + if (!this.roomMap.has(roomId)) { + this.roomMap.set(roomId, { + pinned: new Set(), + widgets: [], + }); + } + } + + protected async onReady(): Promise { + this.matrixClient.on("RoomState.events", this.onRoomStateEvents); + this.matrixClient.getRooms().forEach((room: Room) => { + const pinned = SettingsStore.getValue("Widgets.pinned", room.roomId); + + if (pinned || WidgetUtils.getRoomWidgets(room).length) { + this.initRoom(room.roomId); + } + + if (pinned) { + this.getRoom(room.roomId).pinned = new Set(pinned); + } + + this.loadRoomWidgets(room); + }); + this.emit("update"); + } + + protected async onNotReady(): Promise { + this.matrixClient.off("RoomState.events", this.onRoomStateEvents); + this.widgetMap = new Map(); + this.roomMap = new Map(); + await this.reset({}); + } + + // We don't need this, but our contract says we do. + protected async onAction(payload: ActionPayload) { + return; + } + + private onWidgetEchoStoreUpdate(roomId: string, widgetId: string) { + this.initRoom(roomId); + this.loadRoomWidgets(this.matrixClient.getRoom(roomId)); + this.emit("update"); + } + + private generateApps(room: Room): IApp[] { + return WidgetEchoStore.getEchoedRoomWidgets(room.roomId, WidgetUtils.getRoomWidgets(room)).map((ev) => { + return WidgetUtils.makeAppConfig( + ev.getStateKey(), ev.getContent(), ev.getSender(), ev.getRoomId(), ev.getId(), + ); + }); + } + + private loadRoomWidgets(room: Room) { + const roomInfo = this.roomMap.get(room.roomId); + roomInfo.widgets = []; + this.generateApps(room).forEach(app => { + this.widgetMap.set(app.id, app); + roomInfo.widgets.push(app); + }); + this.emit(room.roomId); + } + + private onRoomStateEvents(ev: MatrixEvent) { + if (ev.getType() !== "im.vector.modular.widgets") return; + const roomId = ev.getRoomId(); + this.initRoom(roomId); + this.loadRoomWidgets(this.matrixClient.getRoom(roomId)); + this.emit("update"); + } + + public getRoomId = (widgetId: string) => { + const app = this.widgetMap.get(widgetId); + if (!app) return null; + return app.roomId; + } + + public getRoom = (roomId: string) => { + return this.roomMap.get(roomId); + }; + + private onPinnedWidgetsChange = (settingName: string, roomId: string) => { + const pinned = SettingsStore.getValue(settingName, roomId); + this.initRoom(roomId); + this.getRoom(roomId).pinned = new Set(pinned); + this.emit(roomId); + this.emit("update"); + }; + + public isPinned(widgetId: string) { + const roomId = this.getRoomId(widgetId); + const roomInfo = this.getRoom(roomId); + // TODO heuristic for Jitsi etc + return roomInfo ? roomInfo.pinned.has(widgetId) : false; + } + + public pinWidget(widgetId: string) { + const roomId = this.getRoomId(widgetId); + const roomInfo = this.getRoom(roomId); + if (!roomInfo) return; + roomInfo.pinned.add(widgetId); + SettingsStore.setValue("Widgets.pinned", roomId, SettingLevel.ROOM_ACCOUNT, Array.from(roomInfo.pinned)); + this.emit(roomId); + this.emit("update"); + } + + public unpinWidget(widgetId: string) { + const roomId = this.getRoomId(widgetId); + const roomInfo = this.getRoom(roomId); + if (!roomInfo) return; + roomInfo.pinned.delete(widgetId); + SettingsStore.setValue("Widgets.pinned", roomId, SettingLevel.ROOM_ACCOUNT, Array.from(roomInfo.pinned)); + this.emit(roomId); + this.emit("update"); + } + + public getApps(room: Room, pinned?: boolean): IApp[] { + const apps = this.getRoom(room.roomId).widgets; + if (pinned) { + return apps.filter(app => this.isPinned(app.id)); + } + return apps + } +} + +window.mxWidgetStore = WidgetStore.instance; From 31cca5e0f2a4b8249eb3cc5b48409781b087b2ca Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 08:48:03 +0100 Subject: [PATCH 134/286] Create new right panel cards --- res/css/_components.scss | 3 + res/css/views/right_panel/_BaseCard.scss | 164 +++++++++++++ .../views/right_panel/_RoomSummaryCard.scss | 157 ++++++++++++ res/css/views/right_panel/_WidgetCard.scss | 25 ++ res/css/views/rooms/_RoomHeader.scss | 12 - src/components/structures/RightPanel.js | 41 +++- src/components/views/right_panel/BaseCard.tsx | 93 +++++++ .../views/right_panel/HeaderButtons.tsx | 3 +- .../views/right_panel/RoomHeaderButtons.tsx | 43 ++-- .../views/right_panel/RoomSummaryCard.tsx | 231 ++++++++++++++++++ .../views/right_panel/WidgetCard.tsx | 107 ++++++++ .../payloads/SetRightPanelPhasePayload.ts | 1 + src/settings/Settings.ts | 4 + src/stores/RightPanelStorePhases.ts | 3 + 14 files changed, 844 insertions(+), 43 deletions(-) create mode 100644 res/css/views/right_panel/_BaseCard.scss create mode 100644 res/css/views/right_panel/_RoomSummaryCard.scss create mode 100644 res/css/views/right_panel/_WidgetCard.scss create mode 100644 src/components/views/right_panel/BaseCard.tsx create mode 100644 src/components/views/right_panel/RoomSummaryCard.tsx create mode 100644 src/components/views/right_panel/WidgetCard.tsx diff --git a/res/css/_components.scss b/res/css/_components.scss index 45ed6b3300..27ec1088c3 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -155,9 +155,12 @@ @import "./views/messages/_UnknownBody.scss"; @import "./views/messages/_ViewSourceEvent.scss"; @import "./views/messages/_common_CryptoEvent.scss"; +@import "./views/right_panel/_BaseCard.scss"; @import "./views/right_panel/_EncryptionInfo.scss"; +@import "./views/right_panel/_RoomSummaryCard.scss"; @import "./views/right_panel/_UserInfo.scss"; @import "./views/right_panel/_VerificationPanel.scss"; +@import "./views/right_panel/_WidgetCard.scss"; @import "./views/room_settings/_AliasSettings.scss"; @import "./views/rooms/_AppsDrawer.scss"; @import "./views/rooms/_Autocomplete.scss"; diff --git a/res/css/views/right_panel/_BaseCard.scss b/res/css/views/right_panel/_BaseCard.scss new file mode 100644 index 0000000000..c66b7261e3 --- /dev/null +++ b/res/css/views/right_panel/_BaseCard.scss @@ -0,0 +1,164 @@ +/* +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_BaseCard { + padding: 0 8px; + overflow: hidden; + display: flex; + flex-direction: column; + flex: 1; + + .mx_BaseCard_header { + margin: 8px 0; + + h2 { + margin: 0 44px; + font-size: $font-18px; + font-weight: $font-semi-bold; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .mx_BaseCard_back, .mx_BaseCard_close { + position: absolute; + background-color: rgba(141, 151, 165, 0.2); // TODO + height: 20px; + width: 20px; + margin: 12px; + top: 0; + + &::before { + content: ""; + position: absolute; + height: 16px; + width: 16px; + top: 2px; + left: 2px; + mask-repeat: no-repeat; + mask-position: center; + background-color: $rightpanel-button-color; + } + } + + .mx_BaseCard_back { + border-radius: 4px; + left: 0; + + &::before { + transform: rotate(90deg); + mask-size: 20px; + mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); // TODO + } + } + + .mx_BaseCard_close { + border-radius: 10px; + right: 0; + + &::before { + mask-image: url('$(res)/img/icons-close.svg'); // TODO + } + } + } + + .mx_AutoHideScrollbar { + // collapse the margin into a padding to move the scrollbar into the right gutter + margin-right: -8px; + padding-right: 8px; + min-height: 0; + width: 100%; + height: 100%; + } + + .mx_BaseCard_Group { + margin: 20px 0 16px; + + & > * { + margin-left: 10px; + margin-right: 10px; + } + + h1 { + color: $tertiary-fg-color; + font-size: $font-12px; + font-weight: 500; + } + + .mx_BaseCard_Button { + padding: 10px 38px 10px 12px; + margin: 0; + position: relative; + font-size: $font-13px; + height: 20px; + line-height: 20px; + border-radius: 8px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + + &:hover { + background-color: rgba(141, 151, 165, 0.1); + } + + &::after { + content: ''; + position: absolute; + top: 10px; + right: 6px; + height: 20px; + width: 20px; + mask-repeat: no-repeat; + mask-position: center; + background-color: $icon-button-color; + transform: rotate(270deg); + mask-size: 20px; + mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); // TODO + } + } + } + + .mx_BaseCard_footer { + padding-top: 4px; + text-align: center; + + .mx_AccessibleButton_kind_secondary { + color: $secondary-fg-color; + background-color: rgba(141, 151, 165, 0.2); // TODO + font-weight: $font-semi-bold; + font-size: $font-14px; + min-width: 70px; + + & + .mx_AccessibleButton_kind_secondary { + margin-left: 16px; + } + } + } +} + +.mx_FilePanel, +.mx_UserInfo, +.mx_NotificationPanel, +.mx_MemberList { + &.mx_BaseCard { + padding: 32px 0 0; + + .mx_AutoHideScrollbar { + margin-right: unset; + padding-right: unset; + } + } +} diff --git a/res/css/views/right_panel/_RoomSummaryCard.scss b/res/css/views/right_panel/_RoomSummaryCard.scss new file mode 100644 index 0000000000..3c46727348 --- /dev/null +++ b/res/css/views/right_panel/_RoomSummaryCard.scss @@ -0,0 +1,157 @@ +/* +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_RoomSummaryCard { + // shrink left gutter by 12 and instead add it as margin to all things except the buttons + // as their hover effect should go into the gutter + & > * { // TODO consolidate this as the standard effect + margin-left: 10px; + margin-right: 10px; + } + .mx_AutoHideScrollbar { + margin-left: 0; + } + + .mx_BaseCard_header { + text-align: center; + margin-top: 20px; + + h2 { + font-weight: $font-semi-bold; + font-size: $font-18px; + margin: 12px 0 4px; + } + + .mx_RoomSummaryCard_alias { + font-size: $font-13px; + color: $secondary-fg-color; + } + + h2, .mx_RoomSummaryCard_alias { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + } + + .mx_RoomSummaryCard_avatar { + display: inline-flex; + + .mx_RoomSummaryCard_e2ee { + display: inline-block; + position: relative; + width: 54px; + height: 54px; + border-radius: 50%; + background-color: #737D8C; + margin-top: -3px; // alignment + margin-left: -10px; // overlap + border: 3px solid $dark-panel-bg-color; + + &::before { + content: ''; + position: absolute; + top: 13px; + left: 13px; + height: 28px; + width: 28px; + mask-size: cover; + mask-repeat: no-repeat; + mask-position: center; + mask-image: url('$(res)/img/e2e/disabled.svg'); + background-color: #ffffff; + } + } + + .mx_RoomSummaryCard_e2ee_secure{ + background-color: #5ABFF2; + &::before { + mask-image: url('$(res)/img/e2e/normal.svg'); + } + } + } + } + + .mx_RoomSummaryCard_aboutGroup { + .mx_RoomSummaryCard_Button { + padding-left: 48px; + + &::before { + content: ''; + position: absolute; + top: 8px; + left: 8px; + height: 24px; + width: 24px; + mask-repeat: no-repeat; + mask-position: center; + background-color: $icon-button-color; + } + } + } + + .mx_RoomSummaryCard_appsGroup { + .mx_RoomSummaryCard_Button { + padding-left: 10px; + color: $tertiary-fg-color; + + span { + color: $primary-fg-color; + } + + img { + vertical-align: top; + margin-right: 18px; + border-radius: 4px; + } + + &::before { + content: unset; + } + } + + .mx_RoomSummaryCard_icon_app_pinned::after { + mask-image: url('$(res)/img/element-icons/room/pin2.svg'); + background-color: $accent-color; + transform: unset; + } + } + + .mx_AccessibleButton_kind_link { + padding: 0; + margin-top: 12px; + margin-bottom: 12px; + font-size: $font-13px; + font-weight: $font-semi-bold; + } +} + +.mx_RoomSummaryCard_icon_people::before { + mask-image: url("$(res)/img/element-icons/room/members.svg"); +} + +.mx_RoomSummaryCard_icon_files::before { + mask-image: url('$(res)/img/element-icons/room/files.svg'); +} + +.mx_RoomSummaryCard_icon_share::before { + mask-image: url('$(res)/img/element-icons/room/share.svg'); +} + +.mx_RoomSummaryCard_icon_settings::before { + mask-image: url('$(res)/img/element-icons/settings.svg'); +} diff --git a/res/css/views/right_panel/_WidgetCard.scss b/res/css/views/right_panel/_WidgetCard.scss new file mode 100644 index 0000000000..b863c53c33 --- /dev/null +++ b/res/css/views/right_panel/_WidgetCard.scss @@ -0,0 +1,25 @@ +/* +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_WidgetCard { + height: 100%; + display: contents; + + .mx_AppTileFullWidth { + height: 100%; + border: 0; + } +} diff --git a/res/css/views/rooms/_RoomHeader.scss b/res/css/views/rooms/_RoomHeader.scss index a880a7bee2..d240877507 100644 --- a/res/css/views/rooms/_RoomHeader.scss +++ b/res/css/views/rooms/_RoomHeader.scss @@ -236,10 +236,6 @@ limitations under the License. } } -.mx_RoomHeader_settingsButton::before { - mask-image: url('$(res)/img/element-icons/settings.svg'); -} - .mx_RoomHeader_forgetButton::before { mask-image: url('$(res)/img/element-icons/leave.svg'); width: 26px; @@ -249,14 +245,6 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/room/search-inset.svg'); } -.mx_RoomHeader_shareButton::before { - mask-image: url('$(res)/img/element-icons/room/share.svg'); -} - -.mx_RoomHeader_manageIntegsButton::before { - mask-image: url('$(res)/img/element-icons/room/integrations.svg'); -} - .mx_RoomHeader_showPanel { height: 16px; } diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js index b2b1ceae00..57c0f46fad 100644 --- a/src/components/structures/RightPanel.js +++ b/src/components/structures/RightPanel.js @@ -32,6 +32,9 @@ import {RightPanelPhases, RIGHT_PANEL_PHASES_NO_ARGS} from "../../stores/RightPa import RightPanelStore from "../../stores/RightPanelStore"; import MatrixClientContext from "../../contexts/MatrixClientContext"; import {Action} from "../../dispatcher/actions"; +import RoomSummaryCard from "../views/right_panel/RoomSummaryCard"; +import WidgetCard from "../views/right_panel/WidgetCard"; +import defaultDispatcher from "../../dispatcher/dispatcher"; export default class RightPanel extends React.Component { static get propTypes() { @@ -182,6 +185,7 @@ export default class RightPanel extends React.Component { event: payload.event, verificationRequest: payload.verificationRequest, verificationRequestPromise: payload.verificationRequestPromise, + widgetId: payload.widgetId, }); } } @@ -209,6 +213,14 @@ export default class RightPanel extends React.Component { } }; + onClose = () => { + // the RightPanelStore has no way of knowing which mode room/group it is in, so we handle closing here + defaultDispatcher.dispatch({ + action: Action.ToggleRightPanel, + type: this.props.groupId ? "group" : "room", + }); + }; + render() { const MemberList = sdk.getComponent('rooms.MemberList'); const UserInfo = sdk.getComponent('right_panel.UserInfo'); @@ -225,17 +237,24 @@ export default class RightPanel extends React.Component { switch (this.state.phase) { case RightPanelPhases.RoomMemberList: if (this.props.room.roomId) { - panel = ; + panel = ; } break; + case RightPanelPhases.GroupMemberList: if (this.props.groupId) { panel = ; } break; + case RightPanelPhases.GroupRoomList: panel = ; break; + case RightPanelPhases.RoomMemberInfo: case RightPanelPhases.EncryptionPanel: panel = ; break; + case RightPanelPhases.Room3pidMemberInfo: panel = ; break; + case RightPanelPhases.GroupMemberInfo: panel = ; break; + case RightPanelPhases.GroupRoomInfo: panel = ; break; + case RightPanelPhases.NotificationPanel: - panel = ; + panel = ; break; + case RightPanelPhases.FilePanel: - panel = ; + panel = ; + break; + + case RightPanelPhases.RoomSummary: + panel = ; + break; + + case RightPanelPhases.Widget: + panel = ; break; } diff --git a/src/components/views/right_panel/BaseCard.tsx b/src/components/views/right_panel/BaseCard.tsx new file mode 100644 index 0000000000..3e95da1bc1 --- /dev/null +++ b/src/components/views/right_panel/BaseCard.tsx @@ -0,0 +1,93 @@ +/* +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, {ReactNode} from 'react'; +import classNames from 'classnames'; + +import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; +import {_t} from "../../../languageHandler"; +import AccessibleButton from "../elements/AccessibleButton"; +import defaultDispatcher from "../../../dispatcher/dispatcher"; +import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPanelPhasePayload"; +import {Action} from "../../../dispatcher/actions"; +import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; + +interface IProps { + header?: ReactNode; + footer?: ReactNode; + className?: string; + withoutScrollContainer?: boolean; + previousPhase?: RightPanelPhases; + onClose?(): void; +} + +interface IGroupProps { + className?: string; + title: string; +} + +export const Group: React.FC = ({ className, title, children }) => { + return

    +

    {title}

    + {children} +
    ; +}; + +const BaseCard: React.FC = ({ + onClose, + className, + header, + footer, + withoutScrollContainer, + previousPhase, + children, +}) => { + let backButton; + if (previousPhase) { + const onBackClick = () => { + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: previousPhase, + }); + }; + backButton = ; + } + + let closeButton; + if (onClose) { + closeButton = ; + } + + if (!withoutScrollContainer) { + children = + { children } + ; + } + + return ( +
    +
    + { backButton } + { closeButton } + { header } +
    + { children } + { footer &&
    { footer }
    } +
    + ); +}; + +export default BaseCard; diff --git a/src/components/views/right_panel/HeaderButtons.tsx b/src/components/views/right_panel/HeaderButtons.tsx index e922959bbb..543c7c067f 100644 --- a/src/components/views/right_panel/HeaderButtons.tsx +++ b/src/components/views/right_panel/HeaderButtons.tsx @@ -96,8 +96,7 @@ export default abstract class HeaderButtons extends React.Component + return
    {this.renderButtons()}
    ; } diff --git a/src/components/views/right_panel/RoomHeaderButtons.tsx b/src/components/views/right_panel/RoomHeaderButtons.tsx index 7d732b8ae3..e19a440005 100644 --- a/src/components/views/right_panel/RoomHeaderButtons.tsx +++ b/src/components/views/right_panel/RoomHeaderButtons.tsx @@ -26,7 +26,10 @@ import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; import {Action} from "../../../dispatcher/actions"; import {ActionPayload} from "../../../dispatcher/payloads"; -const MEMBER_PHASES = [ +const ROOM_INFO_PHASES = [ + RightPanelPhases.RoomSummary, + RightPanelPhases.Widget, + RightPanelPhases.FilePanel, RightPanelPhases.RoomMemberList, RightPanelPhases.RoomMemberInfo, RightPanelPhases.EncryptionPanel, @@ -54,20 +57,10 @@ export default class RoomHeaderButtons extends HeaderButtons { } } - private onMembersClicked = () => { - if (this.state.phase === RightPanelPhases.RoomMemberInfo) { - // send the active phase to trigger a toggle - // XXX: we should pass refireParams here but then it won't collapse as we desire it to - this.setPhase(RightPanelPhases.RoomMemberInfo); - } else { - // This toggles for us, if needed - this.setPhase(RightPanelPhases.RoomMemberList); - } - }; - - private onFilesClicked = () => { + // TODO make it restore whatever widget they were on last + private onRoomSummaryClicked = () => { // This toggles for us, if needed - this.setPhase(RightPanelPhases.FilePanel); + this.setPhase(RightPanelPhases.RoomSummary); }; private onNotificationsClicked = () => { @@ -77,19 +70,17 @@ export default class RoomHeaderButtons extends HeaderButtons { public renderButtons() { return [ - , - , - = ({ children, className, onClick }) => { + return + { children } + ; +}; + +export const useWidgets = (room: Room) => { + const [apps, setApps] = useState(WidgetStore.instance.getApps(room)); + + const updateApps = useCallback(() => { + // Copy the array so that we always trigger a re-render, as some updates mutate the array of apps/settings + setApps([...WidgetStore.instance.getApps(room)]); + }, [room]); + + useEffect(updateApps, [room]); + useEventEmitter(WidgetEchoStore, "update", updateApps); + useEventEmitter(WidgetStore.instance, room.roomId, updateApps); + + return apps; +}; + +const AppsSection: React.FC = ({ room }) => { + const cli = useContext(MatrixClientContext); + const apps = useWidgets(room); + + const onManageIntegrations = () => { + const managers = IntegrationManagers.sharedInstance(); + if (!managers.hasManager()) { + managers.openNoManagerDialog(); + } else { + if (SettingsStore.getValue("feature_many_integration_managers")) { + managers.openAll(room); + } else { + managers.getPrimaryManager().open(room); + } + } + }; + + return + { apps.map(app => { + const name = WidgetUtils.getWidgetName(app); + const dataTitle = WidgetUtils.getWidgetDataTitle(app); + const subtitle = dataTitle && " - " + dataTitle; + + let iconUrls = [require("../../../../res/img/element-icons/room/default_app.svg")]; + // heuristics for some better icons until Widgets support their own icons + if (app.type.includes("meeting") || app.type.includes("calendar")) { + iconUrls = [require("../../../../res/img/element-icons/room/default_cal.svg")]; + } else if (app.type.includes("pad") || app.type.includes("doc") || app.type.includes("calc")) { + iconUrls = [require("../../../../res/img/element-icons/room/default_doc.svg")]; + } else if (app.type.includes("clock")) { + iconUrls = [require("../../../../res/img/element-icons/room/default_clock.svg")]; + } + + if (app.avatar_url) { // MSC2765 + iconUrls.unshift(getHttpUriForMxc(cli.getHomeserverUrl(), app.avatar_url, 20, 20, "crop")); + } + + const isPinned = WidgetStore.instance.isPinned(app.id); + const classes = classNames("mx_RoomSummaryCard_icon_app", { + mx_RoomSummaryCard_icon_app_pinned: isPinned, + }); + + if (isPinned) { + const onClick = () => { + WidgetStore.instance.unpinWidget(app.id); + }; + + return + + {name} + { subtitle } + + } + + const onOpenWidgetClick = () => { + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.Widget, + refireParams: { + widgetId: app.id, + }, + }); + }; + + return ; + }) } + + + { apps.length > 0 ? _t("Edit apps") : _t("Add applications") } + + ; +}; + +const onRoomMembersClick = () => { + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.RoomMemberList, + }); +}; + +const onRoomFilesClick = () => { + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.FilePanel, + }); +}; + +const onRoomSettingsClick = () => { + defaultDispatcher.dispatch({ action: "open_room_settings" }); +}; + +const RoomSummaryCard: React.FC = ({ room, onClose }) => { + const cli = useContext(MatrixClientContext); + + const onShareRoomClick = () => { + Modal.createTrackedDialog('share room dialog', '', ShareDialog, { + target: room, + }); + }; + + const isRoomEncrypted = useIsEncrypted(cli, room); + + const alias = room.getCanonicalAlias() || room.getAltAliases()[0] || ""; + const header = +
    + + +
    + +

    { room.name }

    +
    + { alias } +
    +
    ; + + return + + + + + + + + + ; +}; + +export default RoomSummaryCard; diff --git a/src/components/views/right_panel/WidgetCard.tsx b/src/components/views/right_panel/WidgetCard.tsx new file mode 100644 index 0000000000..c447bb2952 --- /dev/null +++ b/src/components/views/right_panel/WidgetCard.tsx @@ -0,0 +1,107 @@ +/* +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, {useContext, useEffect} from "react"; +import {Room} from "matrix-js-sdk/src/models/room"; + +import MatrixClientContext from "../../../contexts/MatrixClientContext"; +import BaseCard from "./BaseCard"; +import WidgetUtils from "../../../utils/WidgetUtils"; +import AccessibleButton from "../elements/AccessibleButton"; +import AppTile from "../elements/AppTile"; +import {_t} from "../../../languageHandler"; +import {useWidgets} from "./RoomSummaryCard"; +import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; +import defaultDispatcher from "../../../dispatcher/dispatcher"; +import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPanelPhasePayload"; +import {Action} from "../../../dispatcher/actions"; +import {WidgetStore} from "../../../stores/WidgetStore"; + +interface IProps { + room: Room; + widgetId: string; + onClose(): void; +} + +const onFinished = () => { + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.RoomSummary, + }); +} + +const WidgetCard: React.FC = ({ room, widgetId, onClose }) => { + const cli = useContext(MatrixClientContext); + + const apps = useWidgets(room); + const app = apps.find(a => a.id === widgetId); + const isPinned = app && WidgetStore.instance.isPinned(app.id); + + useEffect(() => { + if (!app || isPinned) { + // TODO maybe we should do this in the ActiveWidgetStore instead + onFinished(); + } + }, [app, isPinned]); + + // Don't render anything as we are about to transition + if (!app || isPinned) return null; + + const header = +

    { WidgetUtils.getWidgetName(app) }

    +
    ; + + const onPinClick = () => { + WidgetStore.instance.pinWidget(app.id); + }; + + const onEditClick = () => { + WidgetUtils.editWidget(room, app); + }; + + const footer = + + { _t("Pin to room") } + + + { _t("Edit") } + + ; + + return + + ; +}; + +export default WidgetCard; diff --git a/src/dispatcher/payloads/SetRightPanelPhasePayload.ts b/src/dispatcher/payloads/SetRightPanelPhasePayload.ts index 75dea9f3df..4126e8a669 100644 --- a/src/dispatcher/payloads/SetRightPanelPhasePayload.ts +++ b/src/dispatcher/payloads/SetRightPanelPhasePayload.ts @@ -34,4 +34,5 @@ export interface SetRightPanelPhaseRefireParams { groupRoomId?: string; // XXX: The type for event should 'view_3pid_invite' action's payload event?: any; + widgetId?: string; } diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 95861e11df..43bb989d1f 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -607,4 +607,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: _td("Enable experimental, compact IRC style layout"), default: false, }, + "Widgets.pinned": { + supportedLevels: LEVELS_ROOM_OR_ACCOUNT, + default: [], + }, }; diff --git a/src/stores/RightPanelStorePhases.ts b/src/stores/RightPanelStorePhases.ts index 9045b17193..11b51dfc2d 100644 --- a/src/stores/RightPanelStorePhases.ts +++ b/src/stores/RightPanelStorePhases.ts @@ -22,6 +22,8 @@ export enum RightPanelPhases { NotificationPanel = 'NotificationPanel', RoomMemberInfo = 'RoomMemberInfo', EncryptionPanel = 'EncryptionPanel', + RoomSummary = 'RoomSummary', + Widget = 'Widget', Room3pidMemberInfo = 'Room3pidMemberInfo', // Group stuff @@ -34,6 +36,7 @@ export enum RightPanelPhases { // These are the phases that are safe to persist (the ones that don't require additional // arguments). export const RIGHT_PANEL_PHASES_NO_ARGS = [ + RightPanelPhases.RoomSummary, RightPanelPhases.NotificationPanel, RightPanelPhases.FilePanel, RightPanelPhases.RoomMemberList, From 98b59fb217b705c934355093fe71ec0a63f407d1 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 10:19:51 +0100 Subject: [PATCH 135/286] Consolidate all the work thus far --- res/css/structures/_HeaderButtons.scss | 8 ++ res/css/structures/_MainSplit.scss | 1 + res/css/structures/_RightPanel.scss | 17 ++-- .../context_menus/_IconizedContextMenu.scss | 5 +- res/css/views/right_panel/_BaseCard.scss | 11 +-- res/css/views/right_panel/_UserInfo.scss | 4 +- src/@types/global.d.ts | 2 +- src/components/structures/ContextMenu.tsx | 6 +- src/components/structures/FilePanel.js | 35 ++++++-- .../structures/NotificationPanel.js | 38 +++++---- src/components/structures/RoomView.js | 6 +- .../context_menus/IconizedContextMenu.tsx | 4 +- .../views/context_menus/WidgetContextMenu.js | 11 +++ src/components/views/elements/AppTile.js | 39 +++++---- .../views/elements/ManageIntegsButton.js | 63 -------------- .../views/right_panel/RoomSummaryCard.tsx | 2 +- src/components/views/right_panel/UserInfo.js | 30 +++---- .../views/right_panel/WidgetCard.tsx | 84 +++++++++++++++++-- src/components/views/rooms/AppsDrawer.js | 52 ++---------- src/components/views/rooms/MemberList.js | 51 ++++++----- src/components/views/rooms/RoomHeader.js | 36 -------- src/dispatcher/actions.ts | 10 +++ .../payloads/AppTileActionPayload.ts | 23 +++++ src/i18n/strings/en_EN.json | 33 +++++--- src/stores/WidgetStore.ts | 10 ++- src/utils/WidgetUtils.js | 30 +++++-- 26 files changed, 337 insertions(+), 274 deletions(-) delete mode 100644 src/components/views/elements/ManageIntegsButton.js create mode 100644 src/dispatcher/payloads/AppTileActionPayload.ts diff --git a/res/css/structures/_HeaderButtons.scss b/res/css/structures/_HeaderButtons.scss index 9ef40e9d6a..72b663ef0e 100644 --- a/res/css/structures/_HeaderButtons.scss +++ b/res/css/structures/_HeaderButtons.scss @@ -18,6 +18,14 @@ limitations under the License. display: flex; } +.mx_RoomHeader_buttons + .mx_HeaderButtons { + // remove the | separator line for when next to RoomHeaderButtons + // TODO: remove this once when we redo communities and make the right panel similar to the new rooms one + &::before { + content: unset; + } +} + .mx_HeaderButtons::before { content: ""; background-color: $header-divider-color; diff --git a/res/css/structures/_MainSplit.scss b/res/css/structures/_MainSplit.scss index dc62cb8218..ad1656efbb 100644 --- a/res/css/structures/_MainSplit.scss +++ b/res/css/structures/_MainSplit.scss @@ -25,6 +25,7 @@ limitations under the License. padding: 5px; // margin left to not allow the handle to not encroach on the space for the scrollbar margin-left: 8px; + height: calc(100vh - 51px); // height of .mx_RoomHeader.light-panel &:hover .mx_RightPanel_ResizeHandle { // Need to use important to override element style attributes diff --git a/res/css/structures/_RightPanel.scss b/res/css/structures/_RightPanel.scss index 7e5ab7cdbd..b9b765db56 100644 --- a/res/css/structures/_RightPanel.scss +++ b/res/css/structures/_RightPanel.scss @@ -70,21 +70,16 @@ limitations under the License. } } -.mx_RightPanel_membersButton::before { - mask-image: url('$(res)/img/element-icons/room/members.svg'); - mask-position: center; -} - -.mx_RightPanel_filesButton::before { - mask-image: url('$(res)/img/element-icons/room/files.svg'); - mask-position: center; -} - .mx_RightPanel_notifsButton::before { mask-image: url('$(res)/img/element-icons/notifications.svg'); mask-position: center; } +.mx_RightPanel_roomSummaryButton::before { + mask-image: url('$(res)/img/element-icons/room/room-summary.svg'); + mask-position: center; +} + .mx_RightPanel_groupMembersButton::before { mask-image: url('$(res)/img/element-icons/community-members.svg'); mask-position: center; @@ -144,7 +139,7 @@ limitations under the License. } .mx_RightPanel_empty { - margin-right: -42px; + margin-right: -28px; h2 { font-weight: 700; diff --git a/res/css/views/context_menus/_IconizedContextMenu.scss b/res/css/views/context_menus/_IconizedContextMenu.scss index 7913058995..d911ac6dfe 100644 --- a/res/css/views/context_menus/_IconizedContextMenu.scss +++ b/res/css/views/context_menus/_IconizedContextMenu.scss @@ -82,7 +82,6 @@ limitations under the License. } span.mx_IconizedContextMenu_label { // labels - padding-left: 14px; width: 100%; flex: 1; @@ -91,6 +90,10 @@ limitations under the License. overflow: hidden; white-space: nowrap; } + + .mx_IconizedContextMenu_icon + .mx_IconizedContextMenu_label { + padding-left: 14px; + } } } diff --git a/res/css/views/right_panel/_BaseCard.scss b/res/css/views/right_panel/_BaseCard.scss index c66b7261e3..661f496e8d 100644 --- a/res/css/views/right_panel/_BaseCard.scss +++ b/res/css/views/right_panel/_BaseCard.scss @@ -134,17 +134,18 @@ limitations under the License. .mx_BaseCard_footer { padding-top: 4px; text-align: center; + display: flex; + justify-content: space-around; .mx_AccessibleButton_kind_secondary { color: $secondary-fg-color; background-color: rgba(141, 151, 165, 0.2); // TODO font-weight: $font-semi-bold; font-size: $font-14px; - min-width: 70px; - - & + .mx_AccessibleButton_kind_secondary { - margin-left: 16px; - } + } + + .mx_AccessibleButton_disabled { + cursor: not-allowed; } } } diff --git a/res/css/views/right_panel/_UserInfo.scss b/res/css/views/right_panel/_UserInfo.scss index 6f86d1ad18..9fcf06e5d0 100644 --- a/res/css/views/right_panel/_UserInfo.scss +++ b/res/css/views/right_panel/_UserInfo.scss @@ -15,7 +15,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_UserInfo { +.mx_UserInfo.mx_BaseCard { + // UserInfo has a circular image at the top so it fits between the back & close buttons + padding-top: 0; display: flex; flex-direction: column; flex: 1; diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index de3eb5e767..e1111a8a94 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -29,7 +29,7 @@ import {ActiveRoomObserver} from "../ActiveRoomObserver"; import {Notifier} from "../Notifier"; import type {Renderer} from "react-dom"; import RightPanelStore from "../stores/RightPanelStore"; -import {WidgetStore} from "../stores/WidgetStore"; +import WidgetStore from "../stores/WidgetStore"; declare global { interface Window { diff --git a/src/components/structures/ContextMenu.tsx b/src/components/structures/ContextMenu.tsx index 64e0160d83..884f77aba5 100644 --- a/src/components/structures/ContextMenu.tsx +++ b/src/components/structures/ContextMenu.tsx @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, {CSSProperties, useRef, useState} from "react"; +import React, {CSSProperties, RefObject, useRef, useState} from "react"; import ReactDOM from "react-dom"; import classNames from "classnames"; @@ -416,8 +416,8 @@ export const aboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFace.None return menuOptions; }; -export const useContextMenu = () => { - const button = useRef(null); +export const useContextMenu = (): [boolean, RefObject, () => void, () => void, (val: boolean) => void] => { + const button = useRef(null); const [isOpen, setIsOpen] = useState(false); const open = () => { setIsOpen(true); diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 8aa1192458..8812ba4302 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -23,6 +23,8 @@ import * as sdk from '../../index'; import {MatrixClientPeg} from '../../MatrixClientPeg'; import EventIndexPeg from "../../indexing/EventIndexPeg"; import { _t } from '../../languageHandler'; +import BaseCard from "../views/right_panel/BaseCard"; +import {RightPanelPhases} from "../../stores/RightPanelStorePhases"; /* * Component which shows the filtered file using a TimelinePanel @@ -30,6 +32,7 @@ import { _t } from '../../languageHandler'; class FilePanel extends React.Component { static propTypes = { roomId: PropTypes.string.isRequired, + onClose: PropTypes.func.isRequired, }; // This is used to track if a decrypted event was a live event and should be @@ -188,18 +191,26 @@ class FilePanel extends React.Component { render() { if (MatrixClientPeg.get().isGuest()) { - return
    + return
    { _t("You must register to use this functionality", {}, { 'a': (sub) => { sub } }) }
    -
    ; + ; } else if (this.noRoom) { - return
    + return
    { _t("You must join the room to see its files") }
    -
    ; + ; } // wrap a TimelinePanel with the jump-to-event bits turned off. @@ -215,7 +226,11 @@ class FilePanel extends React.Component { // console.log("rendering TimelinePanel for timelineSet " + this.state.timelineSet.room.roomId + " " + // "(" + this.state.timelineSet._timelines.join(", ") + ")" + " with key " + this.props.roomId); return ( -
    + -
    + ); } else { return ( -
    + -
    + ); } } diff --git a/src/components/structures/NotificationPanel.js b/src/components/structures/NotificationPanel.js index 6ae7f91142..a561ade799 100644 --- a/src/components/structures/NotificationPanel.js +++ b/src/components/structures/NotificationPanel.js @@ -17,14 +17,21 @@ limitations under the License. */ import React from 'react'; +import PropTypes from "prop-types"; + import { _t } from '../../languageHandler'; import {MatrixClientPeg} from "../../MatrixClientPeg"; import * as sdk from "../../index"; +import BaseCard from "../views/right_panel/BaseCard"; /* * Component which shows the global notification list using a TimelinePanel */ class NotificationPanel extends React.Component { + static propTypes = { + onClose: PropTypes.func.isRequired, + }; + render() { // wrap a TimelinePanel with the jump-to-event bits turned off. const TimelinePanel = sdk.getComponent("structures.TimelinePanel"); @@ -35,28 +42,27 @@ class NotificationPanel extends React.Component {

    {_t('You have no visible notifications in this room.')}

    ); + let content; const timelineSet = MatrixClientPeg.get().getNotifTimelineSet(); if (timelineSet) { - return ( -
    - -
    + content = ( + ); } else { console.error("No notifTimelineSet available!"); - return ( -
    - -
    - ); + content = ; } + + return + { content } + ; } } diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index d98a19ebe8..be75f56e1d 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -56,6 +56,7 @@ import MatrixClientContext from "../../contexts/MatrixClientContext"; import { shieldStatusForRoom } from '../../utils/ShieldUtils'; import {Action} from "../../dispatcher/actions"; import {SettingLevel} from "../../settings/SettingLevel"; +import {RightPanelPhases} from "../../stores/RightPanelStorePhases"; const DEBUG = false; let debuglog = function() {}; @@ -1356,7 +1357,10 @@ export default class RoomView extends React.Component { }; onSettingsClick = () => { - dis.dispatch({ action: 'open_room_settings' }); + dis.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.RoomSummary, + }); }; onCancelClick = () => { diff --git a/src/components/views/context_menus/IconizedContextMenu.tsx b/src/components/views/context_menus/IconizedContextMenu.tsx index b3ca9fde6f..a3fb00a9f4 100644 --- a/src/components/views/context_menus/IconizedContextMenu.tsx +++ b/src/components/views/context_menus/IconizedContextMenu.tsx @@ -37,7 +37,7 @@ interface IOptionListProps { } interface IOptionProps extends React.ComponentProps { - iconClassName: string; + iconClassName?: string; } interface ICheckboxProps extends React.ComponentProps { @@ -92,7 +92,7 @@ export const IconizedContextMenuCheckbox: React.FC = ({ export const IconizedContextMenuOption: React.FC = ({label, iconClassName, ...props}) => { return - + { iconClassName && } {label} ; }; diff --git a/src/components/views/context_menus/WidgetContextMenu.js b/src/components/views/context_menus/WidgetContextMenu.js index 1ec74b2e6c..9182b92c8c 100644 --- a/src/components/views/context_menus/WidgetContextMenu.js +++ b/src/components/views/context_menus/WidgetContextMenu.js @@ -26,6 +26,9 @@ export default class WidgetContextMenu extends React.Component { // Callback for when the revoke button is clicked. Required. onRevokeClicked: PropTypes.func.isRequired, + // Callback for when the unpin button is clicked. Required. + onUnpinClicked: PropTypes.func.isRequired, + // Callback for when the snapshot button is clicked. Button not shown // without a callback. onSnapshotClicked: PropTypes.func, @@ -70,6 +73,8 @@ export default class WidgetContextMenu extends React.Component { this.proxyClick(this.props.onRevokeClicked); }; + onUnpinClicked = () => this.proxyClick(this.props.onUnpinClicked); + render() { const options = []; @@ -81,6 +86,12 @@ export default class WidgetContextMenu extends React.Component { ); } + options.push( + + {_t("Unpin")} + , + ); + if (this.props.onReloadClicked) { options.push( diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 1c93841afb..812539ec0d 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -42,6 +42,8 @@ import {WidgetType} from "../../../widgets/WidgetType"; import {Capability} from "../../../widgets/WidgetApi"; import {sleep} from "../../../utils/promise"; import {SettingLevel} from "../../../settings/SettingLevel"; +import WidgetStore from "../../../stores/WidgetStore"; +import {Action} from "../../../dispatcher/actions"; const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:']; const ENABLE_REACT_PERF = false; @@ -315,17 +317,7 @@ export default class AppTile extends React.Component { } _onSnapshotClick() { - console.log("Requesting widget snapshot"); - ActiveWidgetStore.getWidgetMessaging(this.props.app.id).getScreenshot() - .catch((err) => { - console.error("Failed to get screenshot", err); - }) - .then((screenshot) => { - dis.dispatch({ - action: 'picture_snapshot', - file: screenshot, - }, true); - }); + WidgetUtils.snapshotWidget(this.props.app); } /** @@ -406,6 +398,10 @@ export default class AppTile extends React.Component { } } + _onUnpinClicked = () => { + WidgetStore.instance.unpinWidget(this.props.app.id); + } + _onRevokeClicked() { console.info("Revoke widget permissions - %s", this.props.app.id); this._revokeWidgetPermission(); @@ -477,12 +473,20 @@ export default class AppTile extends React.Component { if (payload.widgetId === this.props.app.id) { switch (payload.action) { case 'm.sticker': - if (this._hasCapability('m.sticker')) { - dis.dispatch({action: 'post_sticker_message', data: payload.data}); - } else { - console.warn('Ignoring sticker message. Invalid capability'); - } - break; + if (this._hasCapability('m.sticker')) { + dis.dispatch({action: 'post_sticker_message', data: payload.data}); + } else { + console.warn('Ignoring sticker message. Invalid capability'); + } + break; + + case Action.AppTileDelete: + this._onDeleteClick(); + break; + + case Action.AppTileRevoke: + this._onRevokeClicked(); + break; } } } @@ -826,6 +830,7 @@ export default class AppTile extends React.Component { contextMenu = ( { - ev.preventDefault(); - - const managers = IntegrationManagers.sharedInstance(); - if (!managers.hasManager()) { - managers.openNoManagerDialog(); - } else { - if (SettingsStore.getValue("feature_many_integration_managers")) { - managers.openAll(this.props.room); - } else { - managers.getPrimaryManager().open(this.props.room); - } - } - }; - - render() { - let integrationsButton =
    ; - if (IntegrationManagers.sharedInstance().hasManager()) { - integrationsButton = ( - - ); - } - - return integrationsButton; - } -} - -ManageIntegsButton.propTypes = { - room: PropTypes.object.isRequired, -}; diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index 018b02c879..64743e1bb8 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -39,7 +39,7 @@ import SettingsStore from "../../../settings/SettingsStore"; import TextWithTooltip from "../elements/TextWithTooltip"; import BaseAvatar from "../avatars/BaseAvatar"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; -import {IApp, WidgetStore} from "../../../stores/WidgetStore"; +import WidgetStore, {IApp} from "../../../stores/WidgetStore"; interface IProps { room: Room; diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index 518bb133ce..a79e65cb8b 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -46,6 +46,7 @@ import { useAsyncMemo } from '../../../hooks/useAsyncMemo'; import { verifyUser, legacyVerifyUser, verifyDevice } from '../../../verification'; import {Action} from "../../../dispatcher/actions"; import {useIsEncrypted} from "../../../hooks/useIsEncrypted"; +import BaseCard from "./BaseCard"; const _disambiguateDevices = (devices) => { const names = Object.create(null); @@ -451,7 +452,7 @@ const _isMuted = (member, powerLevelContent) => { return member.powerLevel < levelToSend; }; -const useRoomPowerLevels = (cli, room) => { +export const useRoomPowerLevels = (cli, room) => { const [powerLevels, setPowerLevels] = useState({}); const update = useCallback(() => { @@ -1364,16 +1365,9 @@ const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => { ; }; -const UserInfoHeader = ({onClose, member, e2eStatus}) => { +const UserInfoHeader = ({member, e2eStatus}) => { const cli = useContext(MatrixClientContext); - let closeButton; - if (onClose) { - closeButton = -
    - ; - } - const onMemberAvatarClick = useCallback(() => { const avatarUrl = member.getMxcAvatarUrl ? member.getMxcAvatarUrl() : member.avatarUrl; if (!avatarUrl) return; @@ -1448,7 +1442,6 @@ const UserInfoHeader = ({onClose, member, e2eStatus}) => { const displayName = member.name || member.displayname; return - { closeButton } { avatarElement }
    @@ -1510,15 +1503,16 @@ const UserInfo = ({user, groupId, roomId, onClose, phase=RightPanelPhases.RoomMe break; } - return ( -
    - - + let previousPhase: RightPanelPhases; + // We have no previousPhase for when viewing a UserInfo from a Group or without a Room at this time + if (room) { + previousPhase = RightPanelPhases.RoomMemberList; + } - { content } - -
    - ); + const header = ; + return + { content } + ; }; UserInfo.propTypes = { diff --git a/src/components/views/right_panel/WidgetCard.tsx b/src/components/views/right_panel/WidgetCard.tsx index c447bb2952..8abff36c31 100644 --- a/src/components/views/right_panel/WidgetCard.tsx +++ b/src/components/views/right_panel/WidgetCard.tsx @@ -28,7 +28,15 @@ import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; import defaultDispatcher from "../../../dispatcher/dispatcher"; import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPanelPhasePayload"; import {Action} from "../../../dispatcher/actions"; -import {WidgetStore} from "../../../stores/WidgetStore"; +import WidgetStore from "../../../stores/WidgetStore"; +import ActiveWidgetStore from "../../../stores/ActiveWidgetStore"; +import {ChevronFace, ContextMenuButton, useContextMenu} from "../../structures/ContextMenu"; +import IconizedContextMenu, { + IconizedContextMenuOption, + IconizedContextMenuOptionList, +} from "../context_menus/IconizedContextMenu"; +import {AppTileActionPayload} from "../../../dispatcher/payloads/AppTileActionPayload"; +import {Capability} from "../../../widgets/WidgetApi"; interface IProps { room: Room; @@ -50,6 +58,8 @@ const WidgetCard: React.FC = ({ room, widgetId, onClose }) => { const app = apps.find(a => a.id === widgetId); const isPinned = app && WidgetStore.instance.isPinned(app.id); + const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu(); + useEffect(() => { if (!app || isPinned) { // TODO maybe we should do this in the ActiveWidgetStore instead @@ -64,6 +74,58 @@ const WidgetCard: React.FC = ({ room, widgetId, onClose }) => {

    { WidgetUtils.getWidgetName(app) }

    ; + const canModify = WidgetUtils.canUserModifyWidgets(room.roomId); + + let contextMenu; + if (menuDisplayed) { + let snapshotButton; + if (ActiveWidgetStore.widgetHasCapability(app.id, Capability.Screenshot)) { + const onSnapshotClick = () => { + WidgetUtils.snapshotWidget(app); + closeMenu(); + }; + + snapshotButton = ; + } + + let deleteButton; + if (canModify) { + const onDeleteClick = () => { + defaultDispatcher.dispatch({ + action: Action.AppTileDelete, + widgetId: app.id, + }); + closeMenu(); + }; + + deleteButton = ; + } + + const onRevokeClick = () => { + defaultDispatcher.dispatch({ + action: Action.AppTileRevoke, + widgetId: app.id, + }); + closeMenu(); + }; + + const rect = handle.current.getBoundingClientRect(); + contextMenu = ( + + + { snapshotButton } + { deleteButton } + + + + ); + } + const onPinClick = () => { WidgetStore.instance.pinWidget(app.id); }; @@ -73,12 +135,24 @@ const WidgetCard: React.FC = ({ room, widgetId, onClose }) => { }; const footer = - - { _t("Pin to room") } - - + { _t("Edit") } + + { _t("Pin to room") } + + + ... + + + { contextMenu } ; return { - if (ev.getRoomId() !== this.props.room.roomId || ev.getType() !== 'im.vector.modular.widgets') { - return; - } - this._updateApps(); - }; - - _getApps() { - const widgets = WidgetEchoStore.getEchoedRoomWidgets( - this.props.room.roomId, WidgetUtils.getRoomWidgets(this.props.room), - ); - return widgets.map((ev) => { - return WidgetUtils.makeAppConfig( - ev.getStateKey(), ev.getContent(), ev.getSender(), ev.getRoomId(), ev.getId(), - ); - }); - } + _getApps = () => WidgetStore.instance.getApps(this.props.room, true); _updateApps = () => { - const apps = this._getApps(); this.setState({ - apps: apps, + apps: this._getApps(), }); }; @@ -144,18 +120,6 @@ export default class AppsDrawer extends React.Component { onClickAddWidget = (e) => { e.preventDefault(); - // Display a warning dialog if the max number of widgets have already been added to the room - const apps = this._getApps(); - if (apps && apps.length >= MAX_WIDGETS) { - const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog'); - const errorMsg = `The maximum number of ${MAX_WIDGETS} widgets have already been added to this room.`; - console.error(errorMsg); - Modal.createDialog(ErrorDialog, { - title: _t('Cannot add any more widgets'), - description: _t('The maximum permitted number of widgets have already been added to this room.'), - }); - return; - } this._launchManageIntegrations(); }; diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index 1fdc67eee7..40b3b042b1 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -20,13 +20,14 @@ import React from 'react'; import { _t } from '../../../languageHandler'; import SdkConfig from '../../../SdkConfig'; import dis from '../../../dispatcher/dispatcher'; -import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; import {isValid3pidInvite} from "../../../RoomInvite"; import rate_limited_func from "../../../ratelimitedfunc"; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import * as sdk from "../../../index"; import CallHandler from "../../../CallHandler"; import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore"; +import BaseCard from "../right_panel/BaseCard"; +import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; const INITIAL_LOAD_NUM_MEMBERS = 30; const INITIAL_LOAD_NUM_INVITED = 5; @@ -438,7 +439,13 @@ export default class MemberList extends React.Component { render() { if (this.state.loading) { const Spinner = sdk.getComponent("elements.Spinner"); - return
    ; + return + + ; } const SearchBox = sdk.getComponent('structures.SearchBox'); @@ -485,25 +492,29 @@ export default class MemberList extends React.Component { />; } - return ( -
    - { inviteButton } - -
    - - { invitedHeader } - { invitedSection } -
    -
    - - -
    + const footer = ( + ); + + return +
    + + { invitedHeader } + { invitedSection } +
    +
    ; } onInviteButtonClick = () => { diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 2a44f53d21..1a116838ac 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -18,14 +18,11 @@ limitations under the License. import React, {createRef} from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; -import Modal from "../../../Modal"; import RateLimitedFunc from '../../../ratelimitedfunc'; import { linkifyElement } from '../../../HtmlUtils'; -import ManageIntegsButton from '../elements/ManageIntegsButton'; import {CancelButton} from './SimpleRoomHeader'; import SettingsStore from "../../../settings/SettingsStore"; import RoomHeaderButtons from '../right_panel/RoomHeaderButtons'; @@ -114,13 +111,6 @@ export default class RoomHeader extends React.Component { this.forceUpdate(); }; - onShareRoomClick = (ev) => { - const ShareDialog = sdk.getComponent("dialogs.ShareDialog"); - Modal.createTrackedDialog('share room dialog', '', ShareDialog, { - target: this.props.room, - }); - }; - _hasUnreadPins() { const currentPinEvent = this.props.room.currentState.getStateEvents("m.room.pinned_events", ''); if (!currentPinEvent) return false; @@ -150,7 +140,6 @@ export default class RoomHeader extends React.Component { render() { let searchStatus = null; let cancelButton = null; - let settingsButton = null; let pinnedEventsButton = null; if (this.props.onCancelClick) { @@ -214,14 +203,6 @@ export default class RoomHeader extends React.Component { />; } - if (this.props.onSettingsClick) { - settingsButton = - ; - } - if (this.props.onPinnedClick && SettingsStore.getValue('feature_pinning')) { let pinsIndicator = null; if (this._hasUnreadPins()) { @@ -258,26 +239,9 @@ export default class RoomHeader extends React.Component { title={_t("Search")} />; } - let shareRoomButton; - if (this.props.inRoom) { - shareRoomButton = - ; - } - - let manageIntegsButton; - if (this.props.room && this.props.room.roomId && this.props.inRoom) { - manageIntegsButton = ; - } - const rightRow =
    - { settingsButton } { pinnedEventsButton } - { shareRoomButton } - { manageIntegsButton } { forgetButton } { searchButton }
    ; diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index 6fb71df30d..26d585b76e 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -94,4 +94,14 @@ export enum Action { * Trigged after the phase of the right panel is set. Should be used with AfterRightPanelPhaseChangePayload. */ AfterRightPanelPhaseChange = "after_right_panel_phase_change", + + /** + * Requests that the AppTile deletes the widget. Should be used with the AppTileActionPayload. + */ + AppTileDelete = "appTile_delete", + + /** + * Requests that the AppTile revokes the widget. Should be used with the AppTileActionPayload. + */ + AppTileRevoke = "appTile_revoke", } diff --git a/src/dispatcher/payloads/AppTileActionPayload.ts b/src/dispatcher/payloads/AppTileActionPayload.ts new file mode 100644 index 0000000000..3cdb0f8c1f --- /dev/null +++ b/src/dispatcher/payloads/AppTileActionPayload.ts @@ -0,0 +1,23 @@ +/* +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 AppTileActionPayload extends ActionPayload { + action: Action.AppTileDelete | Action.AppTileRevoke; + widgetId: string; +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 54a4bd6695..9abebfaf60 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1029,8 +1029,6 @@ "Remove %(phone)s?": "Remove %(phone)s?", "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.": "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.", "Phone Number": "Phone Number", - "Cannot add any more widgets": "Cannot add any more widgets", - "The maximum permitted number of widgets have already been added to this room.": "The maximum permitted number of widgets have already been added to this room.", "Add a widget": "Add a widget", "Drop File Here": "Drop File Here", "Drop file here to upload": "Drop file here to upload", @@ -1115,10 +1113,8 @@ "(~%(count)s results)|other": "(~%(count)s results)", "(~%(count)s results)|one": "(~%(count)s result)", "Join Room": "Join Room", - "Settings": "Settings", "Forget room": "Forget room", "Search": "Search", - "Share room": "Share room", "Invites": "Invites", "Favourites": "Favourites", "People": "People", @@ -1135,6 +1131,7 @@ "Can't see what you’re looking for?": "Can't see what you’re looking for?", "Explore all public rooms": "Explore all public rooms", "%(count)s results|other": "%(count)s results", + "%(count)s results|one": "%(count)s result", "This room": "This room", "Joining room …": "Joining room …", "Loading …": "Loading …", @@ -1197,6 +1194,7 @@ "Favourited": "Favourited", "Favourite": "Favourite", "Low Priority": "Low Priority", + "Settings": "Settings", "Leave Room": "Leave Room", "Room options": "Room options", "%(count)s unread messages including mentions.|other": "%(count)s unread messages including mentions.", @@ -1267,6 +1265,7 @@ "URL previews are disabled by default for participants in this room.": "URL previews are disabled by default for participants in this room.", "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.": "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.", "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.": "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.", + "Back": "Back", "Waiting for you to accept on your other session…": "Waiting for you to accept on your other session…", "Waiting for %(displayName)s to accept…": "Waiting for %(displayName)s to accept…", "Accepting…": "Accepting…", @@ -1284,7 +1283,18 @@ "Yours, or the other users’ internet connection": "Yours, or the other users’ internet connection", "Yours, or the other users’ session": "Yours, or the other users’ session", "Members": "Members", - "Files": "Files", + "Room Info": "Room Info", + "Apps": "Apps", + "Unpin app": "Unpin app", + "Edit apps": "Edit apps", + "Add applications": "Add applications", + "Not encrypted": "Not encrypted", + "About": "About", + "%(count)s people|other": "%(count)s people", + "%(count)s people|one": "%(count)s person", + "Show files": "Show files", + "Share room": "Share room", + "Room settings": "Room settings", "Trusted": "Trusted", "Not trusted": "Not trusted", "%(count)s verified sessions|other": "%(count)s verified sessions", @@ -1362,6 +1372,12 @@ "You cancelled verification.": "You cancelled verification.", "Verification cancelled": "Verification cancelled", "Compare emoji": "Compare emoji", + "Reload": "Reload", + "Take a picture": "Take a picture", + "Remove for everyone": "Remove for everyone", + "Remove for me": "Remove for me", + "Edit": "Edit", + "Pin to room": "Pin to room", "Sunday": "Sunday", "Monday": "Monday", "Tuesday": "Tuesday", @@ -1379,7 +1395,6 @@ "Error decrypting audio": "Error decrypting audio", "React": "React", "Reply": "Reply", - "Edit": "Edit", "Message Actions": "Message Actions", "Attachment": "Attachment", "Error decrypting attachment": "Error decrypting attachment", @@ -1490,7 +1505,6 @@ "Download this file": "Download this file", "Information": "Information", "Language Dropdown": "Language Dropdown", - "Manage Integrations": "Manage Integrations", "%(nameList)s %(transitionList)s": "%(nameList)s %(transitionList)s", "%(severalUsers)sjoined %(count)s times|other": "%(severalUsers)sjoined %(count)s times", "%(severalUsers)sjoined %(count)s times|one": "%(severalUsers)sjoined", @@ -1670,7 +1684,6 @@ "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.": "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.": "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.", "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)": "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)", - "Back": "Back", "Send": "Send", "Send Custom Event": "Send Custom Event", "You must specify an event type!": "You must specify an event type!", @@ -1911,10 +1924,8 @@ "Set status": "Set status", "Set a new status...": "Set a new status...", "View Community": "View Community", - "Reload": "Reload", + "Unpin": "Unpin", "Take picture": "Take picture", - "Remove for everyone": "Remove for everyone", - "Remove for me": "Remove for me", "This room is public": "This room is public", "Away": "Away", "User Status": "User Status", diff --git a/src/stores/WidgetStore.ts b/src/stores/WidgetStore.ts index b31fc99515..f6a838344a 100644 --- a/src/stores/WidgetStore.ts +++ b/src/stores/WidgetStore.ts @@ -46,7 +46,7 @@ interface IRoomWidgets { // TODO consolidate WidgetEchoStore into this // TODO consolidate ActiveWidgetStore into this -export class WidgetStore extends AsyncStoreWithClient { +export default class WidgetStore extends AsyncStoreWithClient { private static internalInstance = new WidgetStore(); private widgetMap = new Map(); @@ -159,6 +159,14 @@ export class WidgetStore extends AsyncStoreWithClient { return roomInfo ? roomInfo.pinned.has(widgetId) : false; } + public canPin(widgetId: string) { + // only allow pinning up to a max of two as we do not yet have grid splits + // the only case it will go to three is if you have two and then a Jitsi gets added + const roomId = this.getRoomId(widgetId); + const roomInfo = this.getRoom(roomId); + return roomInfo && roomInfo.pinned.size < 2; + } + public pinWidget(widgetId: string) { const roomId = this.getRoomId(widgetId); const roomInfo = this.getRoom(roomId); diff --git a/src/utils/WidgetUtils.js b/src/utils/WidgetUtils.js index d4ed093a24..771bc0887a 100644 --- a/src/utils/WidgetUtils.js +++ b/src/utils/WidgetUtils.js @@ -479,15 +479,6 @@ export default class WidgetUtils { return url.href; } - static editWidget(room, app) { - // TODO: Open the right manager for the widget - if (SettingsStore.getValue("feature_many_integration_managers")) { - IntegrationManagers.sharedInstance().openAll(room, 'type_' + app.type, app.id); - } else { - IntegrationManagers.sharedInstance().getPrimaryManager().open(room, 'type_' + app.type, app.id); - } - } - static getWidgetName(app) { if (!app || !app.name) return ""; return app.name.trim() || _t("Unknown App"); @@ -497,4 +488,25 @@ export default class WidgetUtils { if (!app || !app.data || !app.data.title) return ""; return app.data.title.trim(); } + + static editWidget(room, app) { + // TODO: Open the right manager for the widget + if (SettingsStore.getValue("feature_many_integration_managers")) { + IntegrationManagers.sharedInstance().openAll(room, 'type_' + app.type, app.id); + } else { + IntegrationManagers.sharedInstance().getPrimaryManager().open(room, 'type_' + app.type, app.id); + } + } + + static snapshotWidget(app) { + console.log("Requesting widget snapshot"); + ActiveWidgetStore.getWidgetMessaging(app.id).getScreenshot().catch((err) => { + console.error("Failed to get screenshot", err); + }).then((screenshot) => { + dis.dispatch({ + action: 'picture_snapshot', + file: screenshot, + }, true); + }); + } } From 0fe6ce18424da21cf8a77045b013af1dfb84bf12 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 11:18:41 +0100 Subject: [PATCH 136/286] Stage svgs --- res/css/views/right_panel/_RoomSummaryCard.scss | 2 +- res/img/element-icons/room/default_app.svg | 11 +++++++++++ res/img/element-icons/room/default_cal.svg | 6 ++++++ res/img/element-icons/room/default_clock.svg | 5 +++++ res/img/element-icons/room/default_doc.svg | 4 ++++ res/img/element-icons/room/pin-upright.svg | 7 +++++++ res/img/element-icons/room/room-summary.svg | 3 +++ 7 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 res/img/element-icons/room/default_app.svg create mode 100644 res/img/element-icons/room/default_cal.svg create mode 100644 res/img/element-icons/room/default_clock.svg create mode 100644 res/img/element-icons/room/default_doc.svg create mode 100644 res/img/element-icons/room/pin-upright.svg create mode 100644 res/img/element-icons/room/room-summary.svg diff --git a/res/css/views/right_panel/_RoomSummaryCard.scss b/res/css/views/right_panel/_RoomSummaryCard.scss index 3c46727348..41df1ca2eb 100644 --- a/res/css/views/right_panel/_RoomSummaryCard.scss +++ b/res/css/views/right_panel/_RoomSummaryCard.scss @@ -125,7 +125,7 @@ limitations under the License. } .mx_RoomSummaryCard_icon_app_pinned::after { - mask-image: url('$(res)/img/element-icons/room/pin2.svg'); + mask-image: url('$(res)/img/element-icons/room/pin-upright.svg'); background-color: $accent-color; transform: unset; } diff --git a/res/img/element-icons/room/default_app.svg b/res/img/element-icons/room/default_app.svg new file mode 100644 index 0000000000..08734170df --- /dev/null +++ b/res/img/element-icons/room/default_app.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/res/img/element-icons/room/default_cal.svg b/res/img/element-icons/room/default_cal.svg new file mode 100644 index 0000000000..5bced115cf --- /dev/null +++ b/res/img/element-icons/room/default_cal.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/res/img/element-icons/room/default_clock.svg b/res/img/element-icons/room/default_clock.svg new file mode 100644 index 0000000000..cc21716d15 --- /dev/null +++ b/res/img/element-icons/room/default_clock.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/element-icons/room/default_doc.svg b/res/img/element-icons/room/default_doc.svg new file mode 100644 index 0000000000..93e7507be3 --- /dev/null +++ b/res/img/element-icons/room/default_doc.svg @@ -0,0 +1,4 @@ + + + + diff --git a/res/img/element-icons/room/pin-upright.svg b/res/img/element-icons/room/pin-upright.svg new file mode 100644 index 0000000000..9297f62a02 --- /dev/null +++ b/res/img/element-icons/room/pin-upright.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/res/img/element-icons/room/room-summary.svg b/res/img/element-icons/room/room-summary.svg new file mode 100644 index 0000000000..b6ac258b18 --- /dev/null +++ b/res/img/element-icons/room/room-summary.svg @@ -0,0 +1,3 @@ + + + From ef0843d4ad00bfd9c3572469351c5d771f858836 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 12:01:44 +0100 Subject: [PATCH 137/286] Iterate to match design --- res/css/views/right_panel/_WidgetCard.scss | 16 +++++++ .../views/right_panel/WidgetCard.tsx | 42 ++++++++++++++++--- src/stores/WidgetStore.ts | 3 +- 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/res/css/views/right_panel/_WidgetCard.scss b/res/css/views/right_panel/_WidgetCard.scss index b863c53c33..0f859738b1 100644 --- a/res/css/views/right_panel/_WidgetCard.scss +++ b/res/css/views/right_panel/_WidgetCard.scss @@ -22,4 +22,20 @@ limitations under the License. height: 100%; border: 0; } + + &.mx_WidgetCard_noEdit { + .mx_AccessibleButton_kind_secondary { + margin: 0 12px; + + &:first-child { + // expand the Pin to room primary action + flex-grow: 1; + } + } + } +} + +.mx_WidgetCard_maxPinnedTooltip { + background-color: $notice-primary-color; + color: #ffffff; } diff --git a/src/components/views/right_panel/WidgetCard.tsx b/src/components/views/right_panel/WidgetCard.tsx index 8abff36c31..bbd02c18fb 100644 --- a/src/components/views/right_panel/WidgetCard.tsx +++ b/src/components/views/right_panel/WidgetCard.tsx @@ -37,6 +37,8 @@ import IconizedContextMenu, { } from "../context_menus/IconizedContextMenu"; import {AppTileActionPayload} from "../../../dispatcher/payloads/AppTileActionPayload"; import {Capability} from "../../../widgets/WidgetApi"; +import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; +import classNames from "classnames"; interface IProps { room: Room; @@ -134,13 +136,39 @@ const WidgetCard: React.FC = ({ room, widgetId, onClose }) => { WidgetUtils.editWidget(room, app); }; - const footer = - + let editButton; + if (canModify) { + editButton = { _t("Edit") } - - + ; + } + + const pinButtonClasses = canModify ? "" : "mx_WidgetCard_widePinButton"; + + let pinButton; + if (WidgetStore.instance.canPin(app.id)) { + pinButton = { _t("Pin to room") } - + ; + } else { + pinButton = + { _t("Pin to room") } + ; + } + + const footer = + { editButton } + { pinButton } = ({ room, widgetId, onClose }) => { return { private constructor() { super(defaultDispatcher, {}); - SettingsStore.watchSetting("Widgets.pinned", IRRELEVANT_ROOM, this.onPinnedWidgetsChange); + SettingsStore.watchSetting("Widgets.pinned", null, this.onPinnedWidgetsChange); WidgetEchoStore.on("update", this.onWidgetEchoStoreUpdate); } From 73dcba1425b3b8d87d9a3bef9c843d52be8fbb15 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 12:02:48 +0100 Subject: [PATCH 138/286] i18n --- src/i18n/strings/en_EN.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9abebfaf60..b3da8cbbb1 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1372,12 +1372,12 @@ "You cancelled verification.": "You cancelled verification.", "Verification cancelled": "Verification cancelled", "Compare emoji": "Compare emoji", - "Reload": "Reload", "Take a picture": "Take a picture", "Remove for everyone": "Remove for everyone", "Remove for me": "Remove for me", "Edit": "Edit", "Pin to room": "Pin to room", + "You can only pin 2 apps at a time": "You can only pin 2 apps at a time", "Sunday": "Sunday", "Monday": "Monday", "Tuesday": "Tuesday", @@ -1925,6 +1925,7 @@ "Set a new status...": "Set a new status...", "View Community": "View Community", "Unpin": "Unpin", + "Reload": "Reload", "Take picture": "Take picture", "This room is public": "This room is public", "Away": "Away", From 3a993434334655a17509b4d45c3c5827484c626b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 15:04:03 +0100 Subject: [PATCH 139/286] iterate styling --- res/css/views/right_panel/_BaseCard.scss | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/res/css/views/right_panel/_BaseCard.scss b/res/css/views/right_panel/_BaseCard.scss index 661f496e8d..ee267d93a5 100644 --- a/res/css/views/right_panel/_BaseCard.scss +++ b/res/css/views/right_panel/_BaseCard.scss @@ -35,7 +35,7 @@ limitations under the License. .mx_BaseCard_back, .mx_BaseCard_close { position: absolute; - background-color: rgba(141, 151, 165, 0.2); // TODO + background-color: rgba(141, 151, 165, 0.2); height: 20px; width: 20px; margin: 12px; @@ -44,10 +44,10 @@ limitations under the License. &::before { content: ""; position: absolute; - height: 16px; - width: 16px; - top: 2px; - left: 2px; + height: 20px; + width: 20px; + top: 0; + left: 0; mask-repeat: no-repeat; mask-position: center; background-color: $rightpanel-button-color; @@ -60,8 +60,8 @@ limitations under the License. &::before { transform: rotate(90deg); - mask-size: 20px; - mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); // TODO + mask-size: 22px; + mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); } } @@ -70,7 +70,8 @@ limitations under the License. right: 0; &::before { - mask-image: url('$(res)/img/icons-close.svg'); // TODO + mask-image: url('$(res)/img/icons-close.svg'); + mask-size: 8px; } } } @@ -126,7 +127,7 @@ limitations under the License. background-color: $icon-button-color; transform: rotate(270deg); mask-size: 20px; - mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); // TODO + mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); } } } @@ -139,11 +140,11 @@ limitations under the License. .mx_AccessibleButton_kind_secondary { color: $secondary-fg-color; - background-color: rgba(141, 151, 165, 0.2); // TODO + background-color: rgba(141, 151, 165, 0.2); font-weight: $font-semi-bold; font-size: $font-14px; } - + .mx_AccessibleButton_disabled { cursor: not-allowed; } From 4a4a8cd6110f4af5eb4795d2efd42a71a4e63b8e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 15:39:58 +0100 Subject: [PATCH 140/286] styling and fix `i` button behaviour --- res/css/structures/_RightPanel.scss | 18 ++++++++---------- .../views/right_panel/RoomHeaderButtons.tsx | 11 ++++++++--- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/res/css/structures/_RightPanel.scss b/res/css/structures/_RightPanel.scss index b9b765db56..5bf0d953f3 100644 --- a/res/css/structures/_RightPanel.scss +++ b/res/css/structures/_RightPanel.scss @@ -68,6 +68,14 @@ limitations under the License. mask-repeat: no-repeat; mask-size: contain; } + + &:hover { + background: rgba($accent-color, 0.1); + + &::before { + background-color: $accent-color; + } + } } .mx_RightPanel_notifsButton::before { @@ -90,16 +98,6 @@ limitations under the License. mask-position: center; } -.mx_RightPanel_headerButton { - &:hover { - background: rgba($accent-color, 0.1); - - &::before { - background-color: $accent-color; - } - } -} - .mx_RightPanel_headerButton_highlight { &::before { background-color: $accent-color !important; diff --git a/src/components/views/right_panel/RoomHeaderButtons.tsx b/src/components/views/right_panel/RoomHeaderButtons.tsx index e19a440005..fa97568fba 100644 --- a/src/components/views/right_panel/RoomHeaderButtons.tsx +++ b/src/components/views/right_panel/RoomHeaderButtons.tsx @@ -19,7 +19,7 @@ limitations under the License. */ import React from 'react'; -import { _t } from '../../../languageHandler'; +import {_t} from '../../../languageHandler'; import HeaderButton from './HeaderButton'; import HeaderButtons, {HeaderKind} from './HeaderButtons'; import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; @@ -59,8 +59,13 @@ export default class RoomHeaderButtons extends HeaderButtons { // TODO make it restore whatever widget they were on last private onRoomSummaryClicked = () => { - // This toggles for us, if needed - this.setPhase(RightPanelPhases.RoomSummary); + if (this.state.phase === RightPanelPhases.Widget) { + // Close the panel + this.setPhase(RightPanelPhases.Widget); + } else { + // This toggles for us, if needed + this.setPhase(RightPanelPhases.RoomSummary); + } }; private onNotificationsClicked = () => { From 8d14d26e2b6841cbb610fa039602df614bcff212 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 16:27:09 +0100 Subject: [PATCH 141/286] do the todos --- .../views/right_panel/_RoomSummaryCard.scss | 8 ++-- .../views/right_panel/RoomHeaderButtons.tsx | 13 ++++-- .../views/right_panel/WidgetCard.tsx | 14 +++--- src/settings/Settings.ts | 2 +- src/stores/WidgetStore.ts | 43 +++++++++++-------- 5 files changed, 45 insertions(+), 35 deletions(-) diff --git a/res/css/views/right_panel/_RoomSummaryCard.scss b/res/css/views/right_panel/_RoomSummaryCard.scss index 41df1ca2eb..84e02af7bc 100644 --- a/res/css/views/right_panel/_RoomSummaryCard.scss +++ b/res/css/views/right_panel/_RoomSummaryCard.scss @@ -17,7 +17,7 @@ limitations under the License. .mx_RoomSummaryCard { // shrink left gutter by 12 and instead add it as margin to all things except the buttons // as their hover effect should go into the gutter - & > * { // TODO consolidate this as the standard effect + & > * { margin-left: 10px; margin-right: 10px; } @@ -57,7 +57,7 @@ limitations under the License. width: 54px; height: 54px; border-radius: 50%; - background-color: #737D8C; + background-color: #737D8C; // TODO margin-top: -3px; // alignment margin-left: -10px; // overlap border: 3px solid $dark-panel-bg-color; @@ -77,8 +77,8 @@ limitations under the License. } } - .mx_RoomSummaryCard_e2ee_secure{ - background-color: #5ABFF2; + .mx_RoomSummaryCard_e2ee_secure { + background-color: #5ABFF2; // TODO &::before { mask-image: url('$(res)/img/e2e/normal.svg'); } diff --git a/src/components/views/right_panel/RoomHeaderButtons.tsx b/src/components/views/right_panel/RoomHeaderButtons.tsx index fa97568fba..338290b7c9 100644 --- a/src/components/views/right_panel/RoomHeaderButtons.tsx +++ b/src/components/views/right_panel/RoomHeaderButtons.tsx @@ -25,6 +25,7 @@ import HeaderButtons, {HeaderKind} from './HeaderButtons'; import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; import {Action} from "../../../dispatcher/actions"; import {ActionPayload} from "../../../dispatcher/payloads"; +import RightPanelStore from "../../../stores/RightPanelStore"; const ROOM_INFO_PHASES = [ RightPanelPhases.RoomSummary, @@ -57,11 +58,15 @@ export default class RoomHeaderButtons extends HeaderButtons { } } - // TODO make it restore whatever widget they were on last private onRoomSummaryClicked = () => { - if (this.state.phase === RightPanelPhases.Widget) { - // Close the panel - this.setPhase(RightPanelPhases.Widget); + // use roomPanelPhase rather than this.state.phase as it remembers the latest one if we close + const lastPhase = RightPanelStore.getSharedInstance().roomPanelPhase; + if (ROOM_INFO_PHASES.includes(lastPhase)) { + if (this.state.phase === lastPhase) { + this.setPhase(lastPhase); + } else { + this.setPhase(lastPhase, RightPanelStore.getSharedInstance().roomPanelPhaseParams); + } } else { // This toggles for us, if needed this.setPhase(RightPanelPhases.RoomSummary); diff --git a/src/components/views/right_panel/WidgetCard.tsx b/src/components/views/right_panel/WidgetCard.tsx index bbd02c18fb..621b1aa1c7 100644 --- a/src/components/views/right_panel/WidgetCard.tsx +++ b/src/components/views/right_panel/WidgetCard.tsx @@ -46,13 +46,6 @@ interface IProps { onClose(): void; } -const onFinished = () => { - defaultDispatcher.dispatch({ - action: Action.SetRightPanelPhase, - phase: RightPanelPhases.RoomSummary, - }); -} - const WidgetCard: React.FC = ({ room, widgetId, onClose }) => { const cli = useContext(MatrixClientContext); @@ -64,8 +57,11 @@ const WidgetCard: React.FC = ({ room, widgetId, onClose }) => { useEffect(() => { if (!app || isPinned) { - // TODO maybe we should do this in the ActiveWidgetStore instead - onFinished(); + // stop showing this card + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.RoomSummary, + }); } }, [app, isPinned]); diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 43bb989d1f..e15cb46145 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -609,6 +609,6 @@ export const SETTINGS: {[setting: string]: ISetting} = { }, "Widgets.pinned": { supportedLevels: LEVELS_ROOM_OR_ACCOUNT, - default: [], + default: {}, }, }; diff --git a/src/stores/WidgetStore.ts b/src/stores/WidgetStore.ts index cf74081633..a43a6bfc30 100644 --- a/src/stores/WidgetStore.ts +++ b/src/stores/WidgetStore.ts @@ -24,6 +24,7 @@ import SettingsStore from "../settings/SettingsStore"; import WidgetEchoStore from "../stores/WidgetEchoStore"; import WidgetUtils from "../utils/WidgetUtils"; import {SettingLevel} from "../settings/SettingLevel"; +import {WidgetType} from "../widgets/WidgetType"; interface IState {} @@ -40,7 +41,7 @@ export interface IApp { interface IRoomWidgets { widgets: IApp[]; - pinned: Set; + pinned: Record; } // TODO consolidate WidgetEchoStore into this @@ -65,7 +66,7 @@ export default class WidgetStore extends AsyncStoreWithClient { private initRoom(roomId: string) { if (!this.roomMap.has(roomId)) { this.roomMap.set(roomId, { - pinned: new Set(), + pinned: {}, widgets: [], }); } @@ -81,7 +82,7 @@ export default class WidgetStore extends AsyncStoreWithClient { } if (pinned) { - this.getRoom(room.roomId).pinned = new Set(pinned); + this.getRoom(room.roomId).pinned = pinned; } this.loadRoomWidgets(room); @@ -144,9 +145,8 @@ export default class WidgetStore extends AsyncStoreWithClient { }; private onPinnedWidgetsChange = (settingName: string, roomId: string) => { - const pinned = SettingsStore.getValue(settingName, roomId); this.initRoom(roomId); - this.getRoom(roomId).pinned = new Set(pinned); + this.getRoom(roomId).pinned = SettingsStore.getValue(settingName, roomId); this.emit(roomId); this.emit("update"); }; @@ -154,8 +154,11 @@ export default class WidgetStore extends AsyncStoreWithClient { public isPinned(widgetId: string) { const roomId = this.getRoomId(widgetId); const roomInfo = this.getRoom(roomId); - // TODO heuristic for Jitsi etc - return roomInfo ? roomInfo.pinned.has(widgetId) : false; + + let pinned = roomInfo && roomInfo.pinned[widgetId]; + // Jitsi widgets should be pinned by default + if (pinned === undefined && WidgetType.JITSI.matches(this.widgetMap.get(widgetId).type)) pinned = true; + return pinned; } public canPin(widgetId: string) { @@ -163,25 +166,31 @@ export default class WidgetStore extends AsyncStoreWithClient { // the only case it will go to three is if you have two and then a Jitsi gets added const roomId = this.getRoomId(widgetId); const roomInfo = this.getRoom(roomId); - return roomInfo && roomInfo.pinned.size < 2; + return roomInfo && Object.keys(roomInfo.pinned).length < 2; } public pinWidget(widgetId: string) { - const roomId = this.getRoomId(widgetId); - const roomInfo = this.getRoom(roomId); - if (!roomInfo) return; - roomInfo.pinned.add(widgetId); - SettingsStore.setValue("Widgets.pinned", roomId, SettingLevel.ROOM_ACCOUNT, Array.from(roomInfo.pinned)); - this.emit(roomId); - this.emit("update"); + this.setPinned(widgetId, true); } public unpinWidget(widgetId: string) { + this.setPinned(widgetId, false); + } + + private setPinned(widgetId: string, value: boolean) { const roomId = this.getRoomId(widgetId); const roomInfo = this.getRoom(roomId); if (!roomInfo) return; - roomInfo.pinned.delete(widgetId); - SettingsStore.setValue("Widgets.pinned", roomId, SettingLevel.ROOM_ACCOUNT, Array.from(roomInfo.pinned)); + roomInfo.pinned[widgetId] = value; + + // Clean up the pinned record + Object.keys(roomInfo).forEach(wId => { + if (!roomInfo.widgets.some(w => w.id === wId)) { + delete roomInfo.pinned[wId]; + } + }); + + SettingsStore.setValue("Widgets.pinned", roomId, SettingLevel.ROOM_ACCOUNT, roomInfo.pinned); this.emit(roomId); this.emit("update"); } From 6c7cb473dc46104687f3cf8ef5356462dea20bf3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 16:33:26 +0100 Subject: [PATCH 142/286] finalise colours from Figma --- res/css/views/right_panel/_RoomSummaryCard.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/css/views/right_panel/_RoomSummaryCard.scss b/res/css/views/right_panel/_RoomSummaryCard.scss index 84e02af7bc..c8c2cceec7 100644 --- a/res/css/views/right_panel/_RoomSummaryCard.scss +++ b/res/css/views/right_panel/_RoomSummaryCard.scss @@ -57,7 +57,7 @@ limitations under the License. width: 54px; height: 54px; border-radius: 50%; - background-color: #737D8C; // TODO + background-color: #737d8c; margin-top: -3px; // alignment margin-left: -10px; // overlap border: 3px solid $dark-panel-bg-color; @@ -78,7 +78,7 @@ limitations under the License. } .mx_RoomSummaryCard_e2ee_secure { - background-color: #5ABFF2; // TODO + background-color: #5abff2; &::before { mask-image: url('$(res)/img/e2e/normal.svg'); } From b982bf87b5a22fbe8c6ea5df48173e9f41a3db6e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 16:33:55 +0100 Subject: [PATCH 143/286] delint --- src/components/views/right_panel/UserInfo.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index a79e65cb8b..84995aeb73 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -31,7 +31,6 @@ import AccessibleButton from '../elements/AccessibleButton'; import SdkConfig from '../../../SdkConfig'; import SettingsStore from "../../../settings/SettingsStore"; import {EventTimeline} from "matrix-js-sdk"; -import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; import RoomViewStore from "../../../stores/RoomViewStore"; import MultiInviter from "../../../utils/MultiInviter"; import GroupStore from "../../../stores/GroupStore"; From 1532048f33a02ae88055c0452926b5c78d3decfb Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 16:49:50 +0100 Subject: [PATCH 144/286] Fix behaviour WidgetStore for new/unknown rooms --- src/stores/WidgetStore.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/stores/WidgetStore.ts b/src/stores/WidgetStore.ts index a43a6bfc30..d29ee81e13 100644 --- a/src/stores/WidgetStore.ts +++ b/src/stores/WidgetStore.ts @@ -196,11 +196,12 @@ export default class WidgetStore extends AsyncStoreWithClient { } public getApps(room: Room, pinned?: boolean): IApp[] { - const apps = this.getRoom(room.roomId).widgets; + const roomInfo = this.getRoom(room.roomId); + if (!roomInfo) return []; if (pinned) { - return apps.filter(app => this.isPinned(app.id)); + return roomInfo.widgets.filter(app => this.isPinned(app.id)); } - return apps + return roomInfo.widgets; } } From 8d03799ffe60f248e289ba51b885a1e22d43cb7d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 16:51:55 +0100 Subject: [PATCH 145/286] Fix create-react-class regression. Can't call setState in c'tor --- src/components/structures/auth/Login.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/structures/auth/Login.js b/src/components/structures/auth/Login.js index 53769fb5a6..a20bf0dd0a 100644 --- a/src/components/structures/auth/Login.js +++ b/src/components/structures/auth/Login.js @@ -124,7 +124,11 @@ export default class LoginComponent extends React.Component { 'm.login.cas': () => this._renderSsoStep("cas"), 'm.login.sso': () => this._renderSsoStep("sso"), }; + } + // TODO: [REACT-WARNING] Replace with appropriate lifecycle event + // eslint-disable-next-line camelcase + UNSAFE_componentWillMount() { this._initLoginLogic(); } From b878c2774125c1f15f2f55678bdb5d3d83c04b88 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 16:56:22 +0100 Subject: [PATCH 146/286] Tidy devDeps, all the webpack stuff lives in the layer above --- package.json | 2 -- yarn.lock | 76 +++------------------------------------------------- 2 files changed, 3 insertions(+), 75 deletions(-) diff --git a/package.json b/package.json index b85191dc22..b403569eb8 100644 --- a/package.json +++ b/package.json @@ -149,7 +149,6 @@ "eslint-plugin-flowtype": "^2.50.3", "eslint-plugin-react": "^7.20.3", "eslint-plugin-react-hooks": "^2.5.1", - "file-loader": "^3.0.1", "glob": "^5.0.15", "jest": "^24.9.0", "jest-canvas-mock": "^2.2.0", @@ -158,7 +157,6 @@ "matrix-react-test-utils": "^0.2.2", "react-test-renderer": "^16.13.1", "rimraf": "^2.7.1", - "source-map-loader": "^0.2.4", "stylelint": "^9.10.1", "stylelint-config-standard": "^18.3.0", "stylelint-scss": "^3.18.0", diff --git a/yarn.lock b/yarn.lock index ec099bbf7c..063b83e0c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1907,17 +1907,7 @@ airbnb-prop-types@^2.15.0: prop-types-exact "^1.2.0" react-is "^16.9.0" -ajv-errors@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" - integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== - -ajv-keywords@^3.1.0: - version "3.4.1" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da" - integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ== - -ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5: +ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5: version "6.12.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd" integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ== @@ -2142,13 +2132,6 @@ async-limiter@~1.0.0: resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== -async@^2.5.0: - version "2.6.3" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" - integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== - dependencies: - lodash "^4.17.14" - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -2294,11 +2277,6 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -big.js@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" - integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== - binary-extensions@^1.0.0: version "1.13.1" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" @@ -2898,15 +2876,6 @@ crc-32@^0.3.0: resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-0.3.0.tgz#6a3d3687f5baec41f7e9b99fe1953a2e5d19775e" integrity sha1-aj02h/W67EH36bmf4ZU6Ll0Zd14= -create-react-class@^15.6.3: - version "15.6.3" - resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.3.tgz#2d73237fb3f970ae6ebe011a9e66f46dbca80036" - integrity sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg== - dependencies: - fbjs "^0.8.9" - loose-envify "^1.3.1" - object-assign "^4.1.1" - cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -3294,11 +3263,6 @@ emojibase-regex@^4.0.1: resolved "https://registry.yarnpkg.com/emojibase-regex/-/emojibase-regex-4.0.1.tgz#a2cd4bbb42825422da9ec72f15e970bc2c90b46a" integrity sha512-S42UHkFfz15i4NNz+wi9iMKFo+B6Kalc6PJLpYX0BUANViXw4vSyYZMFdBGRLduSabWHuEcTLZl9xOa2YP3eJw== -emojis-list@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" - integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== - encoding@^0.1.11: version "0.1.12" resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" @@ -3988,7 +3952,7 @@ fbjs@0.1.0-alpha.7: promise "^7.0.3" whatwg-fetch "^0.9.0" -fbjs@^0.8.4, fbjs@^0.8.9: +fbjs@^0.8.4: version "0.8.17" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90= @@ -4027,14 +3991,6 @@ file-entry-cache@^5.0.1: dependencies: flat-cache "^2.0.1" -file-loader@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-3.0.1.tgz#f8e0ba0b599918b51adfe45d66d1e771ad560faa" - integrity sha512-4sNIOXgtH/9WZq4NvlfU3Opn5ynUsqBwSLyM+I7UOwdGigTBYfVVQEwe/msZNX/j4pCJTIM14Fsw66Svo1oVrw== - dependencies: - loader-utils "^1.0.2" - schema-utils "^1.0.0" - file-saver@^1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-1.3.8.tgz#e68a30c7cb044e2fb362b428469feb291c2e09d8" @@ -5755,15 +5711,6 @@ load-json-file@^4.0.0: pify "^3.0.0" strip-bom "^3.0.0" -loader-utils@^1.0.2, loader-utils@^1.1.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" - integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^1.0.1" - locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" @@ -5841,7 +5788,7 @@ longest-streak@^2.0.1: resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4" integrity sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg== -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1, loose-envify@^1.4.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -7693,15 +7640,6 @@ scheduler@^0.19.1: loose-envify "^1.1.0" object-assign "^4.1.1" -schema-utils@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" - integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g== - dependencies: - ajv "^6.1.0" - ajv-errors "^1.0.0" - ajv-keywords "^3.1.0" - "semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" @@ -7859,14 +7797,6 @@ socks@~2.3.2: ip "1.1.5" smart-buffer "^4.1.0" -source-map-loader@^0.2.4: - version "0.2.4" - resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-0.2.4.tgz#c18b0dc6e23bf66f6792437557c569a11e072271" - integrity sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ== - dependencies: - async "^2.5.0" - loader-utils "^1.1.0" - source-map-resolve@^0.5.0: version "0.5.3" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" From 093ed37c176bcaaddee396732e5e9c111cd0916c Mon Sep 17 00:00:00 2001 From: TERMICO Date: Tue, 8 Sep 2020 14:40:02 +0000 Subject: [PATCH 147/286] Translated using Weblate (Spanish) Currently translated at 88.0% (2073 of 2356 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/element-web/matrix-react-sdk/es/ --- src/i18n/strings/es.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/es.json b/src/i18n/strings/es.json index 891c69ff03..307c8c10c9 100644 --- a/src/i18n/strings/es.json +++ b/src/i18n/strings/es.json @@ -802,7 +802,7 @@ "Old cryptography data detected": "Se detectó información de criptografía antigua", "Data from an older version of %(brand)s has been detected. This will have caused end-to-end cryptography to malfunction in the older version. End-to-end encrypted messages exchanged recently whilst using the older version may not be decryptable in this version. This may also cause messages exchanged with this version to fail. If you experience problems, log out and back in again. To retain message history, export and re-import your keys.": "Se detectó una versión más antigua de %(brand)s. Esto habrá provocado que la criptografía de extremo a extremo funcione incorrectamente en la versión más antigua. Los mensajes cifrados de extremo a extremo intercambiados recientemente mientras usaba la versión más antigua puede que no sean descifrables con esta versión. Esto también puede hacer que fallen con la más reciente. Si experimenta problemas, desconecte y vuelva a ingresar. Para conservar el historial de mensajes, exporte y vuelva a importar sus claves.", "Your Communities": "Sus Comunidades", - "Did you know: you can use communities to filter your %(brand)s experience!": "Sabía que: puede usar comunidades para filtrar su experiencia con %(brand)s", + "Did you know: you can use communities to filter your %(brand)s experience!": "Sabía que: puede usar comunidades para filtrar su experiencia con %(brand)s !", "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 configurar un filtro, arrastre un avatar de comunidad sobre el panel de filtro en la parte izquierda de la pantalla. Puede pulsar sobre un avatar en el panel de filtro en cualquier momento para ver solo las salas y personas asociadas con esa comunidad.", "Error whilst fetching joined communities": "Error al recuperar las comunidades a las que estás unido", "Create a new community": "Crear una comunidad nueva", @@ -1614,7 +1614,7 @@ "Can't find this server or its room list": "No puedo encontrar este servidor o su lista de salas", "All rooms": "Todas las salas", "Your server": "Tu", - "Are you sure you want to remove %(serverName)s": "¿Está seguro de querer eliminar %(serverName)s?", + "Are you sure you want to remove %(serverName)s": "¿Está seguro de querer eliminar %(serverName)s ?", "Remove server": "Quitar servidor", "Matrix": "Matrix", "Add a new server": "Añadir un nuevo servidor", From 27a65fff2b194aed3773b48b9f89d9c2d8dca92d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 17:34:32 +0100 Subject: [PATCH 148/286] null-guard roomId in RightPanel and pass Room to UserView --- src/components/structures/RightPanel.js | 13 +++++++------ src/components/views/right_panel/UserInfo.js | 8 +++----- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js index 11416b29fb..4dd2a7f75e 100644 --- a/src/components/structures/RightPanel.js +++ b/src/components/structures/RightPanel.js @@ -225,11 +225,12 @@ export default class RightPanel extends React.Component { const GroupRoomInfo = sdk.getComponent('groups.GroupRoomInfo'); let panel =
    ; + const roomId = this.props.room ? this.props.room.roomId : undefined; switch (this.state.phase) { case RightPanelPhases.RoomMemberList: - if (this.props.room.roomId) { - panel = ; + if (roomId) { + panel = ; } break; case RightPanelPhases.GroupMemberList: @@ -244,8 +245,8 @@ export default class RightPanel extends React.Component { case RightPanelPhases.EncryptionPanel: panel = ; break; case RightPanelPhases.Room3pidMemberInfo: - panel = ; + panel = ; break; case RightPanelPhases.GroupMemberInfo: panel = ; break; case RightPanelPhases.FilePanel: - panel = ; + panel = ; break; } diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index 518bb133ce..b98211e5c8 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -20,7 +20,7 @@ limitations under the License. import React, {useCallback, useMemo, useState, useEffect, useContext} from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import {Group, RoomMember, User} from 'matrix-js-sdk'; +import {Group, RoomMember, User, Room} from 'matrix-js-sdk'; import dis from '../../../dispatcher/dispatcher'; import Modal from '../../../Modal'; import * as sdk from '../../../index'; @@ -1471,11 +1471,9 @@ const UserInfoHeader = ({onClose, member, e2eStatus}) => { ; }; -const UserInfo = ({user, groupId, roomId, onClose, phase=RightPanelPhases.RoomMemberInfo, ...props}) => { +const UserInfo = ({user, groupId, room, onClose, phase=RightPanelPhases.RoomMemberInfo, ...props}) => { const cli = useContext(MatrixClientContext); - // Load room if we are given a room id and memoize it - this can be undefined for User Info/Group Member Info - const room = useMemo(() => roomId ? cli.getRoom(roomId) : null, [cli, roomId]); // fetch latest room member if we have a room, so we don't show historical information, falling back to user const member = useMemo(() => room ? (room.getMember(user.userId) || user) : user, [room, user]); @@ -1529,7 +1527,7 @@ UserInfo.propTypes = { ]).isRequired, group: PropTypes.instanceOf(Group), groupId: PropTypes.string, - roomId: PropTypes.string, + room: PropTypes.instanceOf(Room), onClose: PropTypes.func, }; From a17b2ba1e559bb9f35764be8ec6097ff27f3da47 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 17:38:13 +0100 Subject: [PATCH 149/286] use constant --- src/stores/WidgetStore.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/stores/WidgetStore.ts b/src/stores/WidgetStore.ts index d29ee81e13..c01d77855c 100644 --- a/src/stores/WidgetStore.ts +++ b/src/stores/WidgetStore.ts @@ -25,6 +25,7 @@ import WidgetEchoStore from "../stores/WidgetEchoStore"; import WidgetUtils from "../utils/WidgetUtils"; import {SettingLevel} from "../settings/SettingLevel"; import {WidgetType} from "../widgets/WidgetType"; +import {UPDATE_EVENT} from "./AsyncStore"; interface IState {} @@ -87,7 +88,7 @@ export default class WidgetStore extends AsyncStoreWithClient { this.loadRoomWidgets(room); }); - this.emit("update"); + this.emit(UPDATE_EVENT); } protected async onNotReady(): Promise { @@ -105,7 +106,7 @@ export default class WidgetStore extends AsyncStoreWithClient { private onWidgetEchoStoreUpdate(roomId: string, widgetId: string) { this.initRoom(roomId); this.loadRoomWidgets(this.matrixClient.getRoom(roomId)); - this.emit("update"); + this.emit(UPDATE_EVENT); } private generateApps(room: Room): IApp[] { @@ -131,7 +132,7 @@ export default class WidgetStore extends AsyncStoreWithClient { const roomId = ev.getRoomId(); this.initRoom(roomId); this.loadRoomWidgets(this.matrixClient.getRoom(roomId)); - this.emit("update"); + this.emit(UPDATE_EVENT); } public getRoomId = (widgetId: string) => { @@ -148,7 +149,7 @@ export default class WidgetStore extends AsyncStoreWithClient { this.initRoom(roomId); this.getRoom(roomId).pinned = SettingsStore.getValue(settingName, roomId); this.emit(roomId); - this.emit("update"); + this.emit(UPDATE_EVENT); }; public isPinned(widgetId: string) { @@ -192,7 +193,7 @@ export default class WidgetStore extends AsyncStoreWithClient { SettingsStore.setValue("Widgets.pinned", roomId, SettingLevel.ROOM_ACCOUNT, roomInfo.pinned); this.emit(roomId); - this.emit("update"); + this.emit(UPDATE_EVENT); } public getApps(room: Room, pinned?: boolean): IApp[] { From 01a8ac25c98d792da0e41601cad64e28411a2538 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 17:40:57 +0100 Subject: [PATCH 150/286] Use null coalescing operator --- src/utils/WidgetUtils.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/utils/WidgetUtils.js b/src/utils/WidgetUtils.js index 771bc0887a..d1daba7ca5 100644 --- a/src/utils/WidgetUtils.js +++ b/src/utils/WidgetUtils.js @@ -480,13 +480,11 @@ export default class WidgetUtils { } static getWidgetName(app) { - if (!app || !app.name) return ""; - return app.name.trim() || _t("Unknown App"); + return app?.name?.trim() || _t("Unknown App"); } static getWidgetDataTitle(app) { - if (!app || !app.data || !app.data.title) return ""; - return app.data.title.trim(); + return app?.data?.title?.trim() || ""; } static editWidget(room, app) { From 596060c5069b76556bbe1b68d9f60af854bd7530 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 17:57:08 +0100 Subject: [PATCH 151/286] fix alignments and iterate PR --- res/css/views/right_panel/_BaseCard.scss | 8 ++++---- .../views/right_panel/_RoomSummaryCard.scss | 18 ++++-------------- .../views/right_panel/RoomSummaryCard.tsx | 12 +++++++----- 3 files changed, 15 insertions(+), 23 deletions(-) diff --git a/res/css/views/right_panel/_BaseCard.scss b/res/css/views/right_panel/_BaseCard.scss index ee267d93a5..26f846fe0a 100644 --- a/res/css/views/right_panel/_BaseCard.scss +++ b/res/css/views/right_panel/_BaseCard.scss @@ -24,7 +24,7 @@ limitations under the License. .mx_BaseCard_header { margin: 8px 0; - h2 { + > h2 { margin: 0 44px; font-size: $font-18px; font-weight: $font-semi-bold; @@ -89,11 +89,11 @@ limitations under the License. margin: 20px 0 16px; & > * { - margin-left: 10px; - margin-right: 10px; + margin-left: 12px; + margin-right: 12px; } - h1 { + > h1 { color: $tertiary-fg-color; font-size: $font-12px; font-weight: 500; diff --git a/res/css/views/right_panel/_RoomSummaryCard.scss b/res/css/views/right_panel/_RoomSummaryCard.scss index c8c2cceec7..78324c5e89 100644 --- a/res/css/views/right_panel/_RoomSummaryCard.scss +++ b/res/css/views/right_panel/_RoomSummaryCard.scss @@ -15,16 +15,6 @@ limitations under the License. */ .mx_RoomSummaryCard { - // shrink left gutter by 12 and instead add it as margin to all things except the buttons - // as their hover effect should go into the gutter - & > * { - margin-left: 10px; - margin-right: 10px; - } - .mx_AutoHideScrollbar { - margin-left: 0; - } - .mx_BaseCard_header { text-align: center; margin-top: 20px; @@ -88,13 +78,13 @@ limitations under the License. .mx_RoomSummaryCard_aboutGroup { .mx_RoomSummaryCard_Button { - padding-left: 48px; + padding-left: 44px; &::before { content: ''; position: absolute; top: 8px; - left: 8px; + left: 10px; height: 24px; width: 24px; mask-repeat: no-repeat; @@ -106,7 +96,7 @@ limitations under the License. .mx_RoomSummaryCard_appsGroup { .mx_RoomSummaryCard_Button { - padding-left: 10px; + padding-left: 12px; color: $tertiary-fg-color; span { @@ -115,7 +105,7 @@ limitations under the License. img { vertical-align: top; - margin-right: 18px; + margin-right: 12px; border-radius: 4px; } diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index 64743e1bb8..83d249b482 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -148,11 +148,13 @@ const AppsSection: React.FC = ({ room }) => { }); }; - return ; + return ( + + ); }) } From 12a6bc8231017b40b3543477b35fc37a87362ec0 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 18:00:21 +0100 Subject: [PATCH 152/286] update copy --- src/components/views/right_panel/RoomSummaryCard.tsx | 2 +- src/i18n/strings/en_EN.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index 83d249b482..544582d206 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -158,7 +158,7 @@ const AppsSection: React.FC = ({ room }) => { }) } - { apps.length > 0 ? _t("Edit apps") : _t("Add applications") } + { apps.length > 0 ? _t("Edit apps") : _t("Add apps") } ; }; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b3da8cbbb1..41d908b4fb 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1287,7 +1287,7 @@ "Apps": "Apps", "Unpin app": "Unpin app", "Edit apps": "Edit apps", - "Add applications": "Add applications", + "Add apps": "Add apps", "Not encrypted": "Not encrypted", "About": "About", "%(count)s people|other": "%(count)s people", From bbe2084f66b4102934db263cb2767ef3443ba11a Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 8 Sep 2020 18:01:56 +0100 Subject: [PATCH 153/286] Add independent set up / reset actions in Settings This adds set up and reset actions to each of cross-signing and secure backup that do separate things, rather than mixing concerns together. (It's temporarily still a bit of lie for backup, as more changes are needed to stop resetting cross-signing as well.) --- .../views/settings/_SecureBackupPanel.scss | 4 ++ src/SecurityManager.js | 2 +- .../CreateSecretStorageDialog.js | 3 + .../views/settings/CrossSigningPanel.js | 51 +++++++++----- .../views/settings/SecureBackupPanel.js | 69 +++++++++++++------ src/i18n/strings/en_EN.json | 6 +- 6 files changed, 90 insertions(+), 45 deletions(-) diff --git a/res/css/views/settings/_SecureBackupPanel.scss b/res/css/views/settings/_SecureBackupPanel.scss index 587cab8f36..a9dab06b57 100644 --- a/res/css/views/settings/_SecureBackupPanel.scss +++ b/res/css/views/settings/_SecureBackupPanel.scss @@ -34,6 +34,10 @@ limitations under the License. .mx_SecureBackupPanel_buttonRow { margin: 1em 0; + + :nth-child(n + 1) { + margin-inline-end: 10px; + } } .mx_SecureBackupPanel_statusList { diff --git a/src/SecurityManager.js b/src/SecurityManager.js index 891f43b705..cc7db3ead7 100644 --- a/src/SecurityManager.js +++ b/src/SecurityManager.js @@ -250,7 +250,7 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f 'Cross-signing keys dialog', '', InteractiveAuthDialog, { title: _t("Setting up keys"), - matrixClient: MatrixClientPeg.get(), + matrixClient: cli, makeRequest, }, ); diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index 07ff3c9b76..d4b1a73c3e 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -280,6 +280,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent { const { forceReset } = this.props; try { + // JRS: In an upcoming change, the cross-signing steps will be + // removed from here and this will instead be about secret storage + // only. if (forceReset) { console.log("Forcing cross-signing and secret storage reset"); await cli.bootstrapSecretStorage({ diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index 8ef68e4b2a..a0ca84645f 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -19,9 +19,9 @@ import React from 'react'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import { _t } from '../../../languageHandler'; import * as sdk from '../../../index'; -import { accessSecretStorage } from '../../../SecurityManager'; import Modal from '../../../Modal'; import Spinner from '../elements/Spinner'; +import InteractiveAuthDialog from '../dialogs/InteractiveAuthDialog'; export default class CrossSigningPanel extends React.PureComponent { constructor(props) { @@ -66,7 +66,7 @@ export default class CrossSigningPanel extends React.PureComponent { }; _onBootstrapClick = () => { - this._bootstrapSecureSecretStorage(false); + this._bootstrapCrossSigning({ forceReset: false }); }; onStatusChanged = () => { @@ -99,35 +99,50 @@ export default class CrossSigningPanel extends React.PureComponent { } /** - * Bootstrapping secret storage may take one of these paths: - * 1. Create secret storage from a passphrase and store cross-signing keys - * in secret storage. + * Bootstrapping cross-signing take one of these paths: + * 1. Create cross-signing keys locally and store in secret storage (if it + * already exists on the account). * 2. Access existing secret storage by requesting passphrase and accessing * cross-signing keys as needed. * 3. All keys are loaded and there's nothing to do. * @param {bool} [forceReset] Bootstrap again even if keys already present */ - _bootstrapSecureSecretStorage = async (forceReset=false) => { + _bootstrapCrossSigning = async ({ forceReset = false }) => { this.setState({ error: null }); try { - await accessSecretStorage(() => undefined, forceReset); + const cli = MatrixClientPeg.get(); + await cli.bootstrapCrossSigning({ + authUploadDeviceSigningKeys: async (makeRequest) => { + const { finished } = Modal.createTrackedDialog( + 'Cross-signing keys dialog', '', InteractiveAuthDialog, + { + title: _t("Setting up keys"), + matrixClient: cli, + makeRequest, + }, + ); + const [confirmed] = await finished; + if (!confirmed) { + throw new Error("Cross-signing key upload auth canceled"); + } + }, + setupNewCrossSigning: forceReset, + }); } catch (e) { this.setState({ error: e }); - console.error("Error bootstrapping secret storage", e); + console.error("Error bootstrapping cross-signing", e); } if (this._unmounted) return; this._getUpdatedStatus(); } - onDestroyStorage = (act) => { - if (!act) return; - this._bootstrapSecureSecretStorage(true); - } - - _destroySecureSecretStorage = () => { + _resetCrossSigning = () => { const ConfirmDestroyCrossSigningDialog = sdk.getComponent("dialogs.ConfirmDestroyCrossSigningDialog"); Modal.createDialog(ConfirmDestroyCrossSigningDialog, { - onFinished: this.onDestroyStorage, + onFinished: (act) => { + if (!act) return; + this._bootstrapCrossSigning({ forceReset: true }); + }, }); } @@ -184,8 +199,8 @@ export default class CrossSigningPanel extends React.PureComponent { if (keysExistAnywhere) { resetButton = (
    - - {_t("Reset cross-signing and secret storage")} + + {_t("Reset")}
    ); @@ -197,7 +212,7 @@ export default class CrossSigningPanel extends React.PureComponent { bootstrapButton = (
    - {_t("Bootstrap cross-signing and secret storage")} + {_t("Set up")}
    ); diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js index 0f43770288..7d4cfc2c10 100644 --- a/src/components/views/settings/SecureBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -25,6 +25,7 @@ import Spinner from '../elements/Spinner'; import AccessibleButton from '../elements/AccessibleButton'; import QuestionDialog from '../dialogs/QuestionDialog'; import RestoreKeyBackupDialog from '../dialogs/keybackup/RestoreKeyBackupDialog'; +import { accessSecretStorage } from '../../../SecurityManager'; export default class SecureBackupPanel extends React.PureComponent { constructor(props) { @@ -184,6 +185,19 @@ export default class SecureBackupPanel extends React.PureComponent { ); } + _resetSecretStorage = async () => { + this.setState({ error: null }); + try { + await accessSecretStorage(() => { }, /* forceReset = */ true); + } catch (e) { + console.error("Error resetting secret storage", e); + if (this._unmounted) return; + this.setState({ error: e }); + } + if (this._unmounted) return; + this._loadBackupStatus(); + } + render() { const { loading, @@ -201,7 +215,7 @@ export default class SecureBackupPanel extends React.PureComponent { let statusDescription; let extraDetailsTableRows; let extraDetails; - let actions; + let actions = []; if (error) { statusDescription = (
    @@ -335,13 +349,6 @@ export default class SecureBackupPanel extends React.PureComponent { trustedLocally = _t("This backup is trusted because it has been restored on this session"); } - let deleteBackupButton; - if (!isSecureBackupRequired()) { - deleteBackupButton = - {_t("Delete Backup")} - ; - } - extraDetailsTableRows = <> {_t("Backup version:")} @@ -359,14 +366,19 @@ export default class SecureBackupPanel extends React.PureComponent {
    {trustedLocally}
    ; - actions = ( -
    - - {restoreButtonCaption} -     - {deleteBackupButton} -
    + actions.push( + + {restoreButtonCaption} + , ); + + if (!isSecureBackupRequired()) { + actions.push( + + {_t("Delete Backup")} + , + ); + } } else { statusDescription = <>

    {_t( @@ -375,12 +387,18 @@ export default class SecureBackupPanel extends React.PureComponent { )}

    {_t("Back up your keys before signing out to avoid losing them.")}

    ; - actions = ( -
    - - {_t("Start using Key Backup")} - -
    + actions.push( + + {_t("Set up")} + , + ); + } + + if (secretStorageKeyInAccount) { + actions.push( + + {_t("Reset")} + , ); } @@ -394,6 +412,13 @@ export default class SecureBackupPanel extends React.PureComponent { } } + let actionRow; + if (actions.length) { + actionRow =
    + {actions} +
    ; + } + return (

    {_t( @@ -430,7 +455,7 @@ export default class SecureBackupPanel extends React.PureComponent { {extraDetails} - {actions} + {actionRow}

    ); } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 1bf431f6e0..b73d2b25fe 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -648,8 +648,7 @@ "Cross-signing is ready for use.": "Cross-signing is ready for use.", "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.": "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.", "Cross-signing is not set up.": "Cross-signing is not set up.", - "Reset cross-signing and secret storage": "Reset cross-signing and secret storage", - "Bootstrap cross-signing and secret storage": "Bootstrap cross-signing and secret storage", + "Reset": "Reset", "Cross-signing public keys:": "Cross-signing public keys:", "in memory": "in memory", "not found": "not found", @@ -748,7 +747,6 @@ "Algorithm:": "Algorithm:", "Your keys are not being backed up from this session.": "Your keys are not being backed up from this session.", "Back up your keys before signing out to avoid losing them.": "Back up your keys before signing out to avoid losing them.", - "Start using Key Backup": "Start using Key Backup", "well formed": "well formed", "unexpected type": "unexpected type", "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.": "Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Recovery Key.", @@ -948,7 +946,6 @@ "Uploaded sound": "Uploaded sound", "Sounds": "Sounds", "Notification sound": "Notification sound", - "Reset": "Reset", "Set a new custom sound": "Set a new custom sound", "Browse": "Browse", "Change room avatar": "Change room avatar", @@ -1173,6 +1170,7 @@ "%(roomName)s is not accessible at this time.": "%(roomName)s is not accessible at this time.", "Try again later, or ask a room admin to check if you have access.": "Try again later, or ask a room admin to check if you have access.", "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please submit a bug report.": "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please submit a bug report.", + "Start using Key Backup": "Start using Key Backup", "Never lose encrypted messages": "Never lose encrypted messages", "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.", "Securely back up your keys to avoid losing them. Learn more.": "Securely back up your keys to avoid losing them. Learn more.", From 421af52e76942bfd3127674d398ba481ffe3cb95 Mon Sep 17 00:00:00 2001 From: Nigel Mansell Date: Tue, 8 Sep 2020 15:29:16 -0400 Subject: [PATCH 154/286] Fixed 1px jump upwards on filter room members box Signed-off-by: Nigel Mansell --- res/css/structures/_SearchBox.scss | 4 ++-- res/css/views/rooms/_MemberList.scss | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/res/css/structures/_SearchBox.scss b/res/css/structures/_SearchBox.scss index 8c36ceee9e..23ee06f7b3 100644 --- a/res/css/structures/_SearchBox.scss +++ b/res/css/structures/_SearchBox.scss @@ -26,8 +26,8 @@ limitations under the License. cursor: pointer; background-image: url('$(res)/img/icons-close.svg'); background-repeat: no-repeat; - width: 15px; - height: 15px; + width: 16px; + height: 16px; background-position: center; padding: 9px; } diff --git a/res/css/views/rooms/_MemberList.scss b/res/css/views/rooms/_MemberList.scss index 90667d41b4..2366667c95 100644 --- a/res/css/views/rooms/_MemberList.scss +++ b/res/css/views/rooms/_MemberList.scss @@ -70,6 +70,10 @@ limitations under the License. } } +.mx_MemberList_query { + height: 16px; +} + .mx_MemberList_wrapper { padding: 10px; } From 644ff56ace9c1f05a5fbeb47e6a8116388e11674 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 9 Sep 2020 09:38:13 +0100 Subject: [PATCH 155/286] Fix e2e tests --- test/end-to-end-tests/src/usecases/room-settings.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/end-to-end-tests/src/usecases/room-settings.js b/test/end-to-end-tests/src/usecases/room-settings.js index 11e2f52c6e..f5fb37167c 100644 --- a/test/end-to-end-tests/src/usecases/room-settings.js +++ b/test/end-to-end-tests/src/usecases/room-settings.js @@ -45,7 +45,9 @@ async function findTabs(session) { /// XXX delay is needed here, possibly because the header is being rerendered /// click doesn't do anything otherwise await session.delay(1000); - const settingsButton = await session.query(".mx_RoomHeader .mx_AccessibleButton[aria-label=Settings]"); + const roomSummaryButton = await session.query('.mx_RoomHeader .mx_AccessibleButton[aria-label="Room Info"]'); + await roomSummaryButton.click(); + const settingsButton = await session.query(".mx_RoomSummaryCard_icon_settings"); await settingsButton.click(); //find tabs From 29c2a0ef35d7de332f61d9396cbf11087139557a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 9 Sep 2020 09:50:08 +0100 Subject: [PATCH 156/286] Fix FilePanel and NotificationPanel overscroll issues --- src/components/structures/FilePanel.js | 1 + src/components/structures/NotificationPanel.js | 2 +- src/components/structures/TimelinePanel.js | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 8812ba4302..6d618d0b9d 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -230,6 +230,7 @@ class FilePanel extends React.Component { className="mx_FilePanel" onClose={this.props.onClose} previousPhase={RightPanelPhases.RoomSummary} + withoutScrollContainer > ; } - return + return { content } ; } diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index daa18bb290..97f9ba48ed 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -104,8 +104,8 @@ class TimelinePanel extends React.Component { // shape property to be passed to EventTiles tileShape: PropTypes.string, - // placeholder text to use if the timeline is empty - empty: PropTypes.string, + // placeholder to use if the timeline is empty + empty: PropTypes.node, // whether to show reactions for an event showReactions: PropTypes.bool, From c8bc80a3b1493d92349c3fd78b09cd5e384c844c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 9 Sep 2020 10:07:43 +0100 Subject: [PATCH 157/286] test with delay --- res/css/views/right_panel/_WidgetCard.scss | 1 + test/end-to-end-tests/src/usecases/room-settings.js | 1 + 2 files changed, 2 insertions(+) diff --git a/res/css/views/right_panel/_WidgetCard.scss b/res/css/views/right_panel/_WidgetCard.scss index 0f859738b1..28f09bf319 100644 --- a/res/css/views/right_panel/_WidgetCard.scss +++ b/res/css/views/right_panel/_WidgetCard.scss @@ -19,6 +19,7 @@ limitations under the License. display: contents; .mx_AppTileFullWidth { + max-width: unset; height: 100%; border: 0; } diff --git a/test/end-to-end-tests/src/usecases/room-settings.js b/test/end-to-end-tests/src/usecases/room-settings.js index f5fb37167c..c7f4495a86 100644 --- a/test/end-to-end-tests/src/usecases/room-settings.js +++ b/test/end-to-end-tests/src/usecases/room-settings.js @@ -47,6 +47,7 @@ async function findTabs(session) { await session.delay(1000); const roomSummaryButton = await session.query('.mx_RoomHeader .mx_AccessibleButton[aria-label="Room Info"]'); await roomSummaryButton.click(); + await session.delay(1000); const settingsButton = await session.query(".mx_RoomSummaryCard_icon_settings"); await settingsButton.click(); From fb0b784369ba2daf66bbe61580bf88bb70a2d408 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 9 Sep 2020 10:56:14 +0100 Subject: [PATCH 158/286] test CI --- test/end-to-end-tests/src/usecases/room-settings.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/end-to-end-tests/src/usecases/room-settings.js b/test/end-to-end-tests/src/usecases/room-settings.js index c7f4495a86..01894ee794 100644 --- a/test/end-to-end-tests/src/usecases/room-settings.js +++ b/test/end-to-end-tests/src/usecases/room-settings.js @@ -47,9 +47,8 @@ async function findTabs(session) { await session.delay(1000); const roomSummaryButton = await session.query('.mx_RoomHeader .mx_AccessibleButton[aria-label="Room Info"]'); await roomSummaryButton.click(); - await session.delay(1000); - const settingsButton = await session.query(".mx_RoomSummaryCard_icon_settings"); - await settingsButton.click(); + // const settingsButton = await session.query(".mx_RoomSummaryCard_icon_settings"); + // await settingsButton.click(); //find tabs const tabButtons = await session.queryAll(".mx_RoomSettingsDialog .mx_TabbedView_tabLabel"); From 37c0d524bcbce5e48da581230c1fad806a9e5baf Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 9 Sep 2020 11:06:15 +0100 Subject: [PATCH 159/286] re-order top right buttons --- .../views/right_panel/RoomHeaderButtons.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/views/right_panel/RoomHeaderButtons.tsx b/src/components/views/right_panel/RoomHeaderButtons.tsx index 338290b7c9..c2364546fd 100644 --- a/src/components/views/right_panel/RoomHeaderButtons.tsx +++ b/src/components/views/right_panel/RoomHeaderButtons.tsx @@ -80,14 +80,6 @@ export default class RoomHeaderButtons extends HeaderButtons { public renderButtons() { return [ - , , + , ]; } } From bb9858714306f4b8976c0f84459bd447a257f5a3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 9 Sep 2020 11:42:41 +0100 Subject: [PATCH 160/286] fix e2e tests. Change the default Room Tab to RoomSummary --- src/settings/Settings.ts | 2 +- test/end-to-end-tests/src/usecases/room-settings.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index e15cb46145..9e0f36b1ba 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -566,7 +566,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { }, "lastRightPanelPhaseForRoom": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, - default: RightPanelPhases.RoomMemberInfo, + default: RightPanelPhases.RoomSummary, }, "lastRightPanelPhaseForGroup": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, diff --git a/test/end-to-end-tests/src/usecases/room-settings.js b/test/end-to-end-tests/src/usecases/room-settings.js index 01894ee794..f5fb37167c 100644 --- a/test/end-to-end-tests/src/usecases/room-settings.js +++ b/test/end-to-end-tests/src/usecases/room-settings.js @@ -47,8 +47,8 @@ async function findTabs(session) { await session.delay(1000); const roomSummaryButton = await session.query('.mx_RoomHeader .mx_AccessibleButton[aria-label="Room Info"]'); await roomSummaryButton.click(); - // const settingsButton = await session.query(".mx_RoomSummaryCard_icon_settings"); - // await settingsButton.click(); + const settingsButton = await session.query(".mx_RoomSummaryCard_icon_settings"); + await settingsButton.click(); //find tabs const tabButtons = await session.queryAll(".mx_RoomSettingsDialog .mx_TabbedView_tabLabel"); From 8dcb2d47199ff26e975424c65c2d100b9d8833d0 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 9 Sep 2020 12:06:16 +0100 Subject: [PATCH 161/286] attempt to fix CI tests --- test/end-to-end-tests/src/usecases/memberlist.js | 5 +++++ test/end-to-end-tests/src/usecases/verify.js | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/test/end-to-end-tests/src/usecases/memberlist.js b/test/end-to-end-tests/src/usecases/memberlist.js index e974eea95b..cc641ae4c9 100644 --- a/test/end-to-end-tests/src/usecases/memberlist.js +++ b/test/end-to-end-tests/src/usecases/memberlist.js @@ -17,6 +17,11 @@ limitations under the License. const assert = require('assert'); +module.exports.openMemberList = async function(session) { + const peopleButton = await session.query(".mx_RoomSummaryCard_icon_people"); + await peopleButton.click(); +}; + async function openMemberInfo(session, name) { const membersAndNames = await getMembersInMemberlist(session); const matchingLabel = membersAndNames.filter((m) => { diff --git a/test/end-to-end-tests/src/usecases/verify.js b/test/end-to-end-tests/src/usecases/verify.js index 98e73ad6b7..4021c97dc9 100644 --- a/test/end-to-end-tests/src/usecases/verify.js +++ b/test/end-to-end-tests/src/usecases/verify.js @@ -16,10 +16,11 @@ limitations under the License. */ const assert = require('assert'); -const {openMemberInfo} = require("./memberlist"); +const {openMemberList, openMemberInfo} = require("./memberlist"); async function startVerification(session, name) { session.log.step("opens their opponent's profile and starts verification"); + await openMemberList(session); await openMemberInfo(session, name); // click verify in member info const firstVerifyButton = await session.query(".mx_UserInfo_verifyButton"); From b635598bc3266190b238e6aa8708ef4ab41c78f8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 9 Sep 2020 12:28:12 +0100 Subject: [PATCH 162/286] Attempt to fix tests and fix RoomSummaryCard having wrong member count --- .../views/right_panel/RoomSummaryCard.tsx | 12 ++++++- .../src/usecases/memberlist.js | 31 +++++++++++-------- test/end-to-end-tests/src/usecases/verify.js | 3 +- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index 544582d206..c782654637 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -181,6 +181,14 @@ const onRoomSettingsClick = () => { defaultDispatcher.dispatch({ action: "open_room_settings" }); }; +const useMemberCount = (room: Room) => { + const [count, setCount] = useState(room.getJoinedMembers().length); + useEventEmitter(room.currentState, "RoomState.members", () => { + setCount(room.getJoinedMembers().length); + }); + return count; +}; + const RoomSummaryCard: React.FC = ({ room, onClose }) => { const cli = useContext(MatrixClientContext); @@ -210,10 +218,12 @@ const RoomSummaryCard: React.FC = ({ room, onClose }) => {
    ; + const memberCount = useMemberCount(room); + return
  • , diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index 97f9ba48ed..8bbc66bf40 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -35,6 +35,7 @@ import Timer from '../../utils/Timer'; import shouldHideEvent from '../../shouldHideEvent'; import EditorStateTransfer from '../../utils/EditorStateTransfer'; import {haveTileForEvent} from "../views/rooms/EventTile"; +import {UIFeature} from "../../settings/UIFeature"; const PAGINATE_SIZE = 20; const INITIAL_SIZE = 20; @@ -1446,6 +1447,7 @@ class TimelinePanel extends React.Component { editState={this.state.editState} showReactions={this.props.showReactions} useIRCLayout={this.props.useIRCLayout} + enableFlair={SettingsStore.getValue(UIFeature.Flair)} /> ); } diff --git a/src/components/views/dialogs/UserSettingsDialog.js b/src/components/views/dialogs/UserSettingsDialog.js index ffde03fe31..8cb5a76760 100644 --- a/src/components/views/dialogs/UserSettingsDialog.js +++ b/src/components/views/dialogs/UserSettingsDialog.js @@ -32,6 +32,7 @@ import FlairUserSettingsTab from "../settings/tabs/user/FlairUserSettingsTab"; import * as sdk from "../../../index"; import SdkConfig from "../../../SdkConfig"; import MjolnirUserSettingsTab from "../settings/tabs/user/MjolnirUserSettingsTab"; +import {UIFeature} from "../../../settings/UIFeature"; export const USER_GENERAL_TAB = "USER_GENERAL_TAB"; export const USER_APPEARANCE_TAB = "USER_APPEARANCE_TAB"; @@ -86,12 +87,14 @@ export default class UserSettingsDialog extends React.Component { "mx_UserSettingsDialog_appearanceIcon", , )); - tabs.push(new Tab( - USER_FLAIR_TAB, - _td("Flair"), - "mx_UserSettingsDialog_flairIcon", - , - )); + if (SettingsStore.getValue(UIFeature.Flair)) { + tabs.push(new Tab( + USER_FLAIR_TAB, + _td("Flair"), + "mx_UserSettingsDialog_flairIcon", + , + )); + } tabs.push(new Tab( USER_NOTIFICATIONS_TAB, _td("Notifications"), diff --git a/src/components/views/elements/EventTilePreview.tsx b/src/components/views/elements/EventTilePreview.tsx index 61e5f5381d..35019a901e 100644 --- a/src/components/views/elements/EventTilePreview.tsx +++ b/src/components/views/elements/EventTilePreview.tsx @@ -21,6 +21,8 @@ import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; import * as Avatar from '../../../Avatar'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import EventTile from '../rooms/EventTile'; +import SettingsStore from "../../../settings/SettingsStore"; +import {UIFeature} from "../../../settings/UIFeature"; interface IProps { /** @@ -121,7 +123,11 @@ export default class EventTilePreview extends React.Component { }); return
    - +
    ; } } diff --git a/src/components/views/elements/ReplyThread.js b/src/components/views/elements/ReplyThread.js index 70592c72c5..2d17c858a2 100644 --- a/src/components/views/elements/ReplyThread.js +++ b/src/components/views/elements/ReplyThread.js @@ -28,6 +28,7 @@ import escapeHtml from "escape-html"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import {Action} from "../../../dispatcher/actions"; import sanitizeHtml from "sanitize-html"; +import {UIFeature} from "../../../settings/UIFeature"; // This component does no cycle detection, simply because the only way to make such a cycle would be to // craft event_id's, using a homeserver that generates predictable event IDs; even then the impact would @@ -366,6 +367,7 @@ export default class ReplyThread extends React.Component { isRedacted={ev.isRedacted()} isTwelveHour={SettingsStore.getValue("showTwelveHourTimestamps")} useIRCLayout={this.props.useIRCLayout} + enableFlair={SettingsStore.getValue(UIFeature.Flair)} /> ; }); diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index ab9f240f2d..f444fb1f1a 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -206,6 +206,9 @@ export default class EventTile extends React.Component { // whether to use the irc layout useIRCLayout: PropTypes.bool, + + // whether or not to show flair at all + enableFlair: PropTypes.bool, }; static defaultProps = { @@ -736,10 +739,10 @@ export default class EventTile extends React.Component { else if (msgtype === 'm.file') text = _td('%(senderName)s uploaded a file'); sender = ; } else { - sender = ; + sender = ; } } diff --git a/src/components/views/rooms/ReplyPreview.js b/src/components/views/rooms/ReplyPreview.js index de70338245..c7872d95ed 100644 --- a/src/components/views/rooms/ReplyPreview.js +++ b/src/components/views/rooms/ReplyPreview.js @@ -22,6 +22,7 @@ import RoomViewStore from '../../../stores/RoomViewStore'; import SettingsStore from "../../../settings/SettingsStore"; import PropTypes from "prop-types"; import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks"; +import {UIFeature} from "../../../settings/UIFeature"; function cancelQuoting() { dis.dispatch({ @@ -80,11 +81,14 @@ export default class ReplyPreview extends React.Component { onClick={cancelQuoting} />
    - +
    ; } diff --git a/src/components/views/rooms/SearchResultTile.js b/src/components/views/rooms/SearchResultTile.js index 136bd23729..8b2a9c2d61 100644 --- a/src/components/views/rooms/SearchResultTile.js +++ b/src/components/views/rooms/SearchResultTile.js @@ -19,6 +19,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import * as sdk from '../../../index'; import {haveTileForEvent} from "./EventTile"; +import SettingsStore from "../../../settings/SettingsStore"; +import {UIFeature} from "../../../settings/UIFeature"; export default class SearchResultTile extends React.Component { static propTypes = { @@ -45,18 +47,27 @@ export default class SearchResultTile extends React.Component { const ret = []; const timeline = result.context.getTimeline(); - for (var j = 0; j < timeline.length; j++) { + for (let j = 0; j < timeline.length; j++) { const ev = timeline[j]; - var highlights; + let highlights; const contextual = (j != result.context.getOurEventIndex()); if (!contextual) { highlights = this.props.searchHighlights; } if (haveTileForEvent(ev)) { - ret.push(); + ret.push(( + + )); } } return ( diff --git a/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js b/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js index 90eb60e632..9b8004d9d6 100644 --- a/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js +++ b/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js @@ -73,6 +73,18 @@ export default class GeneralRoomSettingsTab extends React.Component { urlPreviewSettings = null; } + let flairSection; + if (SettingsStore.getValue(UIFeature.Flair)) { + flairSection = <> + {_t("Flair")} +
    + +
    + ; + } + return (
    {_t("General")}
    @@ -87,14 +99,8 @@ export default class GeneralRoomSettingsTab extends React.Component { canonicalAliasEvent={canonicalAliasEv} aliasEvents={aliasEvents} />
    {_t("Other")}
    - {_t("Flair")} -
    - -
    - - {urlPreviewSettings} + { flairSection } + { urlPreviewSettings } {_t("Leave room")}
    diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 511daf5cc6..c57394d970 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -618,4 +618,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_UI_FEATURE, default: true, }, + [UIFeature.Flair]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, }; diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index e7355a98eb..8cbf7c207b 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -17,4 +17,5 @@ limitations under the License. // see settings.md for documentation on conventions export enum UIFeature { URLPreviews = "UIFeature.urlPreviews", + Flair = "UIFeature.flair", } From 45420ff13bd7e0dbb06367e734c5037304d03292 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 11:27:39 +0100 Subject: [PATCH 235/286] 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 4414077005..767dcd390c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2132,10 +2132,10 @@ "Uploading %(filename)s and %(count)s others|zero": "Uploading %(filename)s", "Uploading %(filename)s and %(count)s others|one": "Uploading %(filename)s and %(count)s other", "Failed to find the general chat for this community": "Failed to find the general chat for this community", + "Feedback": "Feedback", "Notification settings": "Notification settings", "Security & privacy": "Security & privacy", "All settings": "All settings", - "Feedback": "Feedback", "Community settings": "Community settings", "User settings": "User settings", "Switch to light mode": "Switch to light mode", From f4f94e31d184c8c3635a53d1b154f84e1a0bb0a8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 11:38:50 +0100 Subject: [PATCH 236/286] UI Feature Flag: Disable integrations entry UI --- src/SlashCommands.tsx | 3 +++ .../views/right_panel/RoomSummaryCard.tsx | 3 ++- src/components/views/rooms/AuxPanel.js | 24 +++++++++++-------- src/components/views/rooms/MessageComposer.js | 6 ++++- .../tabs/user/GeneralUserSettingsTab.js | 3 +++ src/settings/Settings.ts | 4 ++++ src/settings/UIFeature.ts | 1 + 7 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 7ba2022c6d..a6481d5b95 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -44,6 +44,8 @@ import { ViewUserPayload } from "./dispatcher/payloads/ViewUserPayload"; import { Action } from "./dispatcher/actions"; import { EffectiveMembership, getEffectiveMembership, leaveRoomBehaviour } from "./utils/membership"; import SdkConfig from "./SdkConfig"; +import SettingsStore from "./settings/SettingsStore"; +import {UIFeature} from "./settings/UIFeature"; // XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816 interface HTMLInputEvent extends Event { @@ -797,6 +799,7 @@ export const Commands = [ command: 'addwidget', args: '', description: _td('Adds a custom widget by URL to the room'), + isEnabled: () => SettingsStore.getValue(UIFeature.Widgets), runFn: function(roomId, widgetUrl) { if (!widgetUrl) { return reject(_t("Please supply a widget URL or embed code")); diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index f51f66a5ea..9d20dc1fe1 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -42,6 +42,7 @@ import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import WidgetStore, {IApp} from "../../../stores/WidgetStore"; import { E2EStatus } from "../../../utils/ShieldUtils"; import RoomContext from "../../../contexts/RoomContext"; +import {UIFeature} from "../../../settings/UIFeature"; interface IProps { room: Room; @@ -242,7 +243,7 @@ const RoomSummaryCard: React.FC = ({ room, onClose }) => { - + { SettingsStore.getValue(UIFeature.Widgets) && } ; }; diff --git a/src/components/views/rooms/AuxPanel.js b/src/components/views/rooms/AuxPanel.js index 1f6f104487..f2211dba5c 100644 --- a/src/components/views/rooms/AuxPanel.js +++ b/src/components/views/rooms/AuxPanel.js @@ -28,6 +28,7 @@ import RateLimitedFunc from '../../../ratelimitedfunc'; import SettingsStore from "../../../settings/SettingsStore"; import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; import CallView from "../voip/CallView"; +import {UIFeature} from "../../../settings/UIFeature"; export default class AuxPanel extends React.Component { @@ -198,18 +199,21 @@ export default class AuxPanel extends React.Component { /> ); - const appsDrawer = ; + let appsDrawer; + if (SettingsStore.getValue(UIFeature.Widgets)) { + appsDrawer = ; + } let stateViews = null; if (this.state.counters && SettingsStore.getValue("feature_state_counters")) { - let counters = []; + const counters = []; this.state.counters.forEach((counter, idx) => { const title = counter.title; @@ -218,7 +222,7 @@ export default class AuxPanel extends React.Component { const severity = counter.severity; const stateKey = counter.stateKey; - let span = { title }: { value } + let span = { title }: { value }; if (link) { span = ( diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 922cc2b11e..81c2ae7a33 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -31,6 +31,7 @@ import SettingsStore from "../../../settings/SettingsStore"; import {aboveLeftOf, ContextMenu, ContextMenuTooltipButton, useContextMenu} from "../../structures/ContextMenu"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import ReplyPreview from "./ReplyPreview"; +import {UIFeature} from "../../../settings/UIFeature"; function ComposerAvatar(props) { const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar'); @@ -384,9 +385,12 @@ export default class MessageComposer extends React.Component { permalinkCreator={this.props.permalinkCreator} />, , , - , ); + if (SettingsStore.getValue(UIFeature.Widgets)) { + controls.push(); + } + if (this.state.showCallButtons) { if (callInProgress) { controls.push( diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index 1ebefae590..42e12077f2 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -37,6 +37,7 @@ import {abbreviateUrl} from "../../../../../utils/UrlUtils"; import { getThreepidsWithBindStatus } from '../../../../../boundThreepids'; import Spinner from "../../../elements/Spinner"; import {SettingLevel} from "../../../../../settings/SettingLevel"; +import {UIFeature} from "../../../../../settings/UIFeature"; export default class GeneralUserSettingsTab extends React.Component { static propTypes = { @@ -366,6 +367,8 @@ export default class GeneralUserSettingsTab extends React.Component { } _renderIntegrationManagerSection() { + if (!SettingsStore.getValue(UIFeature.Widgets)) return null; + const SetIntegrationManager = sdk.getComponent("views.settings.SetIntegrationManager"); return ( diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 511daf5cc6..b35fa3db13 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -618,4 +618,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_UI_FEATURE, default: true, }, + [UIFeature.Widgets]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, }; diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index e7355a98eb..99196e5d30 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -17,4 +17,5 @@ limitations under the License. // see settings.md for documentation on conventions export enum UIFeature { URLPreviews = "UIFeature.urlPreviews", + Widgets = "UIFeature.widgets", } From 7a448be1dc59b13a19916fc189123086d5069fbd Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 12:14:33 +0100 Subject: [PATCH 237/286] UI Feature Flag: Disable advanced options and tidy up some copy --- .../views/dialogs/RoomSettingsDialog.js | 15 ++++---- .../tabs/user/PreferencesUserSettingsTab.js | 8 ++--- .../tabs/user/SecurityUserSettingsTab.js | 36 +++++++++++-------- src/settings/Settings.ts | 4 +++ src/settings/UIFeature.ts | 1 + 5 files changed, 40 insertions(+), 24 deletions(-) diff --git a/src/components/views/dialogs/RoomSettingsDialog.js b/src/components/views/dialogs/RoomSettingsDialog.js index 613708e436..a43b284c42 100644 --- a/src/components/views/dialogs/RoomSettingsDialog.js +++ b/src/components/views/dialogs/RoomSettingsDialog.js @@ -29,6 +29,7 @@ import * as sdk from "../../../index"; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import dis from "../../../dispatcher/dispatcher"; import SettingsStore from "../../../settings/SettingsStore"; +import {UIFeature} from "../../../settings/UIFeature"; export const ROOM_GENERAL_TAB = "ROOM_GENERAL_TAB"; export const ROOM_SECURITY_TAB = "ROOM_SECURITY_TAB"; @@ -96,12 +97,14 @@ export default class RoomSettingsDialog extends React.Component { )); } - tabs.push(new Tab( - ROOM_ADVANCED_TAB, - _td("Advanced"), - "mx_RoomSettingsDialog_warningIcon", - , - )); + if (SettingsStore.getValue(UIFeature.AdvancedSettings)) { + tabs.push(new Tab( + ROOM_ADVANCED_TAB, + _td("Advanced"), + "mx_RoomSettingsDialog_warningIcon", + , + )); + } return tabs; } diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index d5dafe146a..347cfccd56 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -50,10 +50,10 @@ export default class PreferencesUserSettingsTab extends React.Component { 'showAvatarChanges', 'showDisplaynameChanges', 'showImages', + 'Pill.shouldShowPillAvatar', ]; - static ADVANCED_SETTINGS = [ - 'Pill.shouldShowPillAvatar', + static GENERAL_SETTINGS = [ 'TagPanel.enableTagPanel', 'promptBeforeInviteUnknownUsers', // Start automatically after startup (electron-only) @@ -191,8 +191,8 @@ export default class PreferencesUserSettingsTab extends React.Component {
    - {_t("Advanced")} - {this._renderGroup(PreferencesUserSettingsTab.ADVANCED_SETTINGS)} + {_t("General")} + {this._renderGroup(PreferencesUserSettingsTab.GENERAL_SETTINGS)} {minimizeToTrayOption} {autoHideMenuOption} {autoLaunchOption} diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js index c25ac438e0..6509fece13 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js @@ -30,6 +30,8 @@ import dis from "../../../../../dispatcher/dispatcher"; import {privateShouldBeEncrypted} from "../../../../../createRoom"; import {SettingLevel} from "../../../../../settings/SettingLevel"; import SecureBackupPanel from "../../SecureBackupPanel"; +import SettingsStore from "../../../../../settings/SettingsStore"; +import {UIFeature} from "../../../../../settings/UIFeature"; export class IgnoredUser extends React.Component { static propTypes = { @@ -311,15 +313,13 @@ export default class SecurityUserSettingsTab extends React.Component { // can remove this. const CrossSigningPanel = sdk.getComponent('views.settings.CrossSigningPanel'); const crossSigning = ( -
    - {_t("Cross-signing")} -
    - -
    +
    + {_t("Cross-signing")} +
    +
    - ); - - const E2eAdvancedPanel = sdk.getComponent('views.settings.E2eAdvancedPanel'); +
    + ); let warning; if (!privateShouldBeEncrypted()) { @@ -329,6 +329,19 @@ export default class SecurityUserSettingsTab extends React.Component {
    ; } + const E2eAdvancedPanel = sdk.getComponent('views.settings.E2eAdvancedPanel'); + let advancedSection; + if (SettingsStore.getValue(UIFeature.AdvancedSettings)) { + advancedSection = <> +
    {_t("Advanced")}
    +
    + {this._renderIgnoredUsers()} + {this._renderManageInvites()} + +
    + ; + } + return (
    {warning} @@ -375,12 +388,7 @@ export default class SecurityUserSettingsTab extends React.Component {
    -
    {_t("Advanced")}
    -
    - {this._renderIgnoredUsers()} - {this._renderManageInvites()} - -
    + { advancedSection }
    ); } diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index b35fa3db13..5fd5eebca4 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -622,4 +622,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_UI_FEATURE, default: true, }, + [UIFeature.AdvancedSettings]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, }; diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index 99196e5d30..d94b44e2c2 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -18,4 +18,5 @@ limitations under the License. export enum UIFeature { URLPreviews = "UIFeature.urlPreviews", Widgets = "UIFeature.widgets", + AdvancedSettings = "UIFeature.advancedSettings", } From 2bea8457e9050782da88fffed0bf3b45b943976c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 12:55:04 +0100 Subject: [PATCH 238/286] UI Feature Flag: Communities --- .../settings/tabs/user/PreferencesUserSettingsTab.js | 9 +++------ src/settings/Settings.ts | 8 ++++++++ src/settings/SettingsStore.ts | 5 +++++ src/settings/UIFeature.ts | 1 + src/settings/controllers/SettingController.ts | 7 +++++++ src/settings/controllers/UIFeatureController.ts | 10 +++++++--- 6 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index d5dafe146a..b3a5d06707 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -23,7 +23,6 @@ import Field from "../../../elements/Field"; import * as sdk from "../../../../.."; import PlatformPeg from "../../../../../PlatformPeg"; import {SettingLevel} from "../../../../../settings/SettingLevel"; -import {UIFeature} from "../../../../../settings/UIFeature"; export default class PreferencesUserSettingsTab extends React.Component { static ROOM_LIST_SETTINGS = [ @@ -138,12 +137,10 @@ export default class PreferencesUserSettingsTab extends React.Component { }; _renderGroup(settingIds) { - if (!SettingsStore.getValue(UIFeature.URLPreviews)) { - settingIds = settingIds.filter(i => i !== 'urlPreviewsEnabled'); - } - const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag"); - return settingIds.map(i => ); + return settingIds.filter(SettingsStore.isEnabled).map(i => { + return ; + }); } render() { diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index c57394d970..5c4dc2e4d0 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -337,6 +337,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: _td('Enable Community Filter Panel'), default: true, invertedSettingName: 'TagPanel.disableTagPanel', + // We force the value to true because the invertedSettingName causes it to flip + controller: new UIFeatureController(UIFeature.Communities, true), }, "theme": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, @@ -621,5 +623,11 @@ export const SETTINGS: {[setting: string]: ISetting} = { [UIFeature.Flair]: { supportedLevels: LEVELS_UI_FEATURE, default: true, + // Disable Flair when Communities are disabled + controller: new UIFeatureController(UIFeature.Communities), + }, + [UIFeature.Communities]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, }, }; diff --git a/src/settings/SettingsStore.ts b/src/settings/SettingsStore.ts index 9e146ad799..498a2d269d 100644 --- a/src/settings/SettingsStore.ts +++ b/src/settings/SettingsStore.ts @@ -257,6 +257,11 @@ export default class SettingsStore { return SETTINGS[settingName].isFeature; } + public static isEnabled(settingName: string) { + if (!SETTINGS[settingName]) return false; + return SETTINGS[settingName].controller ? !SETTINGS[settingName].controller.settingDisabled : true; + } + /** * Gets the value of a setting. The room ID is optional if the setting is not to * be applied to any particular room, otherwise it should be supplied. diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index 8cbf7c207b..3bd7e0f25b 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -18,4 +18,5 @@ limitations under the License. export enum UIFeature { URLPreviews = "UIFeature.urlPreviews", Flair = "UIFeature.flair", + Communities = "UIFeature.communities", } diff --git a/src/settings/controllers/SettingController.ts b/src/settings/controllers/SettingController.ts index d90eba1e9e..ba78597da7 100644 --- a/src/settings/controllers/SettingController.ts +++ b/src/settings/controllers/SettingController.ts @@ -55,4 +55,11 @@ export default abstract class SettingController { public onChange(level: SettingLevel, roomId: string, newValue: any) { // do nothing by default } + + /** + * Gets whether the setting has been disabled due to this controller. + */ + public get settingDisabled() { + return false; + } } diff --git a/src/settings/controllers/UIFeatureController.ts b/src/settings/controllers/UIFeatureController.ts index ed6598a6e8..2748eec16a 100644 --- a/src/settings/controllers/UIFeatureController.ts +++ b/src/settings/controllers/UIFeatureController.ts @@ -26,7 +26,7 @@ import SettingsStore from "../SettingsStore"; * Settings using this controller are assumed to return `false` when disabled. */ export default class UIFeatureController extends SettingController { - public constructor(private uiFeatureName: string) { + public constructor(private uiFeatureName: string, private forcedValue = false) { super(); } @@ -36,10 +36,14 @@ export default class UIFeatureController extends SettingController { calculatedValue: any, calculatedAtLevel: SettingLevel, ): any { - if (!SettingsStore.getValue(this.uiFeatureName)) { + if (this.settingDisabled) { // per the docs: we force a disabled state when the feature isn't active - return false; + return this.forcedValue; } return null; // no override } + + public get settingDisabled(): boolean { + return !SettingsStore.getValue(this.uiFeatureName); + } } From d1070c05ddd3f7bf3f7c2171f4c5c831466a200b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 13:40:27 +0100 Subject: [PATCH 239/286] UI Feature Flag: Disable VoIP --- src/components/structures/MatrixChat.tsx | 3 +++ .../views/dialogs/UserSettingsDialog.js | 17 +++++++++++------ src/settings/Settings.ts | 5 +++++ src/settings/UIFeature.ts | 1 + 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index dde5dc6fb2..48dc8a79d1 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -79,6 +79,7 @@ import { SettingLevel } from "../../settings/SettingLevel"; import { leaveRoomBehaviour } from "../../utils/membership"; import CreateCommunityPrototypeDialog from "../views/dialogs/CreateCommunityPrototypeDialog"; import ThreepidInviteStore, { IThreepidInvite, IThreepidInviteWireFormat } from "../../stores/ThreepidInviteStore"; +import {UIFeature} from "../../settings/UIFeature"; /** constants for MatrixChat.state.view */ export enum Views { @@ -1373,6 +1374,8 @@ export default class MatrixChat extends React.PureComponent { }); }); cli.on('Call.incoming', function(call) { + // Check if the VoIP UI has been disabled + if (!SettingsStore.getValue(UIFeature.Voip)) return; // we dispatch this synchronously to make sure that the event // handlers on the call are set up immediately (so that if // we get an immediate hangup, we don't get a stuck call) diff --git a/src/components/views/dialogs/UserSettingsDialog.js b/src/components/views/dialogs/UserSettingsDialog.js index ffde03fe31..f74f57b970 100644 --- a/src/components/views/dialogs/UserSettingsDialog.js +++ b/src/components/views/dialogs/UserSettingsDialog.js @@ -32,6 +32,7 @@ import FlairUserSettingsTab from "../settings/tabs/user/FlairUserSettingsTab"; import * as sdk from "../../../index"; import SdkConfig from "../../../SdkConfig"; import MjolnirUserSettingsTab from "../settings/tabs/user/MjolnirUserSettingsTab"; +import {UIFeature} from "../../../settings/UIFeature"; export const USER_GENERAL_TAB = "USER_GENERAL_TAB"; export const USER_APPEARANCE_TAB = "USER_APPEARANCE_TAB"; @@ -104,12 +105,16 @@ export default class UserSettingsDialog extends React.Component { "mx_UserSettingsDialog_preferencesIcon", , )); - tabs.push(new Tab( - USER_VOICE_TAB, - _td("Voice & Video"), - "mx_UserSettingsDialog_voiceIcon", - , - )); + + if (SettingsStore.getValue(UIFeature.Voip)) { + tabs.push(new Tab( + USER_VOICE_TAB, + _td("Voice & Video"), + "mx_UserSettingsDialog_voiceIcon", + , + )); + } + tabs.push(new Tab( USER_SECURITY_TAB, _td("Security & Privacy"), diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index b35fa3db13..a7250982bf 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -588,6 +588,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { "showCallButtonsInComposer": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG, default: true, + controller: new UIFeatureController(UIFeature.Voip), }, "e2ee.manuallyVerifyAllSessions": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, @@ -622,4 +623,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_UI_FEATURE, default: true, }, + [UIFeature.Voip]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, }; diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index 99196e5d30..ce174ec4b5 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -18,4 +18,5 @@ limitations under the License. export enum UIFeature { URLPreviews = "UIFeature.urlPreviews", Widgets = "UIFeature.widgets", + Voip = "UIFeature.voip", } From dfabe79335b9024a9efd68d6a68bec0589b2c15c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 13:44:24 +0100 Subject: [PATCH 240/286] tidy up event handler --- src/components/structures/MatrixChat.tsx | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 48dc8a79d1..7a207dd9a5 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1373,17 +1373,19 @@ export default class MatrixChat extends React.PureComponent { ready: true, }); }); - cli.on('Call.incoming', function(call) { - // Check if the VoIP UI has been disabled - if (!SettingsStore.getValue(UIFeature.Voip)) return; - // we dispatch this synchronously to make sure that the event - // handlers on the call are set up immediately (so that if - // we get an immediate hangup, we don't get a stuck call) - dis.dispatch({ - action: 'incoming_call', - call: call, - }, true); - }); + + if (SettingsStore.getValue(UIFeature.Voip)) { + cli.on('Call.incoming', function(call) { + // we dispatch this synchronously to make sure that the event + // handlers on the call are set up immediately (so that if + // we get an immediate hangup, we don't get a stuck call) + dis.dispatch({ + action: 'incoming_call', + call: call, + }, true); + }); + } + cli.on('Session.logged_out', function(errObj) { if (Lifecycle.isLoggingOut()) return; From d3c84e25f5287f7793f9faa723bf88520d6a861a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 14:45:34 +0100 Subject: [PATCH 241/286] UI Feature Flag: Identity server --- src/components/views/dialogs/InviteDialog.js | 85 ++++++++++++++----- .../tabs/user/GeneralUserSettingsTab.js | 11 ++- src/settings/Settings.ts | 4 + src/settings/UIFeature.ts | 1 + 4 files changed, 76 insertions(+), 25 deletions(-) diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index 80d8f1fc2c..3347a1381f 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -38,6 +38,8 @@ import {Action} from "../../../dispatcher/actions"; import {DefaultTagID} from "../../../stores/room-list/models"; import RoomListStore from "../../../stores/room-list/RoomListStore"; import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore"; +import SettingsStore from "../../../settings/SettingsStore"; +import {UIFeature} from "../../../settings/UIFeature"; // we have a number of types defined from the Matrix spec which can't reasonably be altered here. /* eslint-disable camelcase */ @@ -549,7 +551,7 @@ export default class InviteDialog extends React.PureComponent { if (this.state.filterText.startsWith('@')) { // Assume mxid newMember = new DirectoryMember({user_id: this.state.filterText, display_name: null, avatar_url: null}); - } else { + } else if (SettingsStore.getValue(UIFeature.IdentityServer)) { // Assume email newMember = new ThreepidMember(this.state.filterText); } @@ -734,7 +736,7 @@ export default class InviteDialog extends React.PureComponent { this.setState({tryingIdentityServer: true}); return; } - if (term.indexOf('@') > 0 && Email.looksValid(term)) { + if (term.indexOf('@') > 0 && Email.looksValid(term) && SettingsStore.getValue(UIFeature.IdentityServer)) { // Start off by suggesting the plain email while we try and resolve it // to a real account. this.setState({ @@ -1037,7 +1039,9 @@ export default class InviteDialog extends React.PureComponent { } _renderIdentityServerWarning() { - if (!this.state.tryingIdentityServer || this.state.canUseIdentityServer) { + if (!this.state.tryingIdentityServer || this.state.canUseIdentityServer || + !SettingsStore.getValue(UIFeature.IdentityServer) + ) { return null; } @@ -1086,22 +1090,41 @@ export default class InviteDialog extends React.PureComponent { let buttonText; let goButtonFn; + const identityServersEnabled = SettingsStore.getValue(UIFeature.IdentityServer); + const userId = MatrixClientPeg.get().getUserId(); if (this.props.kind === KIND_DM) { title = _t("Direct Messages"); - helpText = _t( + + if (identityServersEnabled) { + helpText = _t( "Start a conversation with someone using their name, username (like ) or email address.", {}, {userId: () => { - return {userId}; - }}, - ); + return ( + {userId} + ); + }}, + ); + } else { + helpText = _t( + "Start a conversation with someone using their name or username (like ).", + {}, + {userId: () => { + return ( + {userId} + ); + }}, + ); + } + if (CommunityPrototypeStore.instance.getSelectedCommunityId()) { const communityName = CommunityPrototypeStore.instance.getSelectedCommunityName(); - helpText = _t( - "Start a conversation with someone using their name, username (like ) or email address. " + - "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click " + - "here.", + + helpText = + { helpText } {_t( + "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, " + + "click here", {communityName}, { userId: () => { return ( @@ -1120,23 +1143,39 @@ export default class InviteDialog extends React.PureComponent { >{sub}
    ); }, - }, - ); + })} + ; } buttonText = _t("Go"); goButtonFn = this._startDm; } else { // KIND_INVITE title = _t("Invite to this room"); - helpText = _t( - "Invite someone using their name, username (like ), email address or share this room.", - {}, - { - userId: () => - {userId}, - a: (sub) => - {sub}, - }, - ); + + if (identityServersEnabled) { + helpText = _t( + "Invite someone using their name, username (like ), email address or " + + "share this room.", + {}, + { + userId: () => + {userId}, + a: (sub) => + {sub}, + }, + ); + } else { + helpText = _t( + "Invite someone using their name, username (like ) or share this room.", + {}, + { + userId: () => + {userId}, + a: (sub) => + {sub}, + }, + ); + } + buttonText = _t("Invite"); goButtonFn = this._inviteUsers; } diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index 42e12077f2..40fd57d311 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -386,14 +386,21 @@ export default class GeneralUserSettingsTab extends React.Component { width="18" height="18" alt={_t("Warning")} /> : null; + let discoverySection; + if (SettingsStore.getValue(UIFeature.IdentityServer)) { + discoverySection = <> +
    {discoWarning} {_t("Discovery")}
    + {this._renderDiscoverySection()} + ; + } + return (
    {_t("General")}
    {this._renderProfileSection()} {this._renderAccountSection()} {this._renderLanguageSection()} -
    {discoWarning} {_t("Discovery")}
    - {this._renderDiscoverySection()} + { discoverySection } {this._renderIntegrationManagerSection() /* Has its own title */}
    {_t("Deactivate account")}
    {this._renderManagementSection()} diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index b35fa3db13..a18d0f2187 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -622,4 +622,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_UI_FEATURE, default: true, }, + [UIFeature.IdentityServer]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, }; diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index 99196e5d30..4de1d954d1 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -18,4 +18,5 @@ limitations under the License. export enum UIFeature { URLPreviews = "UIFeature.urlPreviews", Widgets = "UIFeature.widgets", + IdentityServer = "UIFeature.identityServer", } From 1c44f15d2d4457127852fc82ae8c2c8dc28156db Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 14:54:30 +0100 Subject: [PATCH 242/286] i18n --- src/components/views/dialogs/InviteDialog.js | 6 +++--- src/i18n/strings/en_EN.json | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index 3347a1381f..86411c43da 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -1098,9 +1098,9 @@ export default class InviteDialog extends React.PureComponent { if (identityServersEnabled) { helpText = _t( - "Start a conversation with someone using their name, username (like ) or email address.", - {}, - {userId: () => { + "Start a conversation with someone using their name, username (like ) or email address.", + {}, + {userId: () => { return ( {userId} ); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4414077005..7c5212444c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -832,8 +832,8 @@ "Account management": "Account management", "Deactivating your account is a permanent action - be careful!": "Deactivating your account is a permanent action - be careful!", "Deactivate Account": "Deactivate Account", - "General": "General", "Discovery": "Discovery", + "General": "General", "Deactivate account": "Deactivate account", "Legal": "Legal", "Credits": "Credits", @@ -1732,9 +1732,11 @@ "Recently Direct Messaged": "Recently Direct Messaged", "Direct Messages": "Direct Messages", "Start a conversation with someone using their name, username (like ) or email address.": "Start a conversation with someone using their name, username (like ) or email address.", - "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.", + "Start a conversation with someone using their name or username (like ).": "Start a conversation with someone using their name or username (like ).", + "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here": "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here", "Go": "Go", "Invite someone using their name, username (like ), email address or share this room.": "Invite someone using their name, username (like ), email address or share this room.", + "Invite someone using their name, username (like ) or share this room.": "Invite someone using their name, username (like ) or share this room.", "a new master key signature": "a new master key signature", "a new cross-signing key signature": "a new cross-signing key signature", "a device cross-signing signature": "a device cross-signing signature", From aa25bad68955bdab93f8e83f6cb06cf66dde6c7a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 14:57:46 +0100 Subject: [PATCH 243/286] tidy --- src/components/views/dialogs/InviteDialog.js | 51 ++++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index 86411c43da..f66de67a1d 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -1108,9 +1108,9 @@ export default class InviteDialog extends React.PureComponent { ); } else { helpText = _t( - "Start a conversation with someone using their name or username (like ).", - {}, - {userId: () => { + "Start a conversation with someone using their name or username (like ).", + {}, + {userId: () => { return ( {userId} ); @@ -1120,30 +1120,29 @@ export default class InviteDialog extends React.PureComponent { if (CommunityPrototypeStore.instance.getSelectedCommunityId()) { const communityName = CommunityPrototypeStore.instance.getSelectedCommunityName(); - - helpText = - { helpText } {_t( - "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, " + - "click here", + const inviteText = _t("This won't invite them to %(communityName)s. " + + "To invite someone to %(communityName)s, click here", {communityName}, { - userId: () => { - return ( - {userId} - ); - }, - a: (sub) => { - return ( - {sub} - ); - }, - })} + userId: () => { + return ( + {userId} + ); + }, + a: (sub) => { + return ( + {sub} + ); + }, + }); + helpText = + { helpText } {inviteText} ; } buttonText = _t("Go"); From 7bd5e3fa310238ed57a806564a4e8cd8cd9cca6c Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 10 Sep 2020 13:56:07 +0100 Subject: [PATCH 244/286] Move security-related dialogs to a common directory --- res/css/_components.scss | 10 +++++----- .../_AccessSecretStorageDialog.scss | 0 .../_CreateKeyBackupDialog.scss | 0 .../_CreateSecretStorageDialog.scss | 0 .../_KeyBackupFailedDialog.scss | 0 .../_RestoreKeyBackupDialog.scss | 0 src/SecurityManager.js | 7 +++---- .../{keybackup => security}/CreateKeyBackupDialog.js | 0 .../CreateSecretStorageDialog.js | 2 +- .../dialogs/{ => security}/ExportE2eKeysDialog.js | 6 +++--- .../IgnoreRecoveryReminderDialog.js | 0 .../dialogs/{ => security}/ImportE2eKeysDialog.js | 6 +++--- .../NewRecoveryMethodDialog.js | 2 +- .../RecoveryMethodRemovedDialog.js | 0 src/components/structures/MatrixChat.tsx | 4 ++-- src/components/structures/auth/E2eSetup.js | 2 +- src/components/views/dialogs/LogoutDialog.js | 8 ++++---- .../AccessSecretStorageDialog.js | 0 .../ConfirmDestroyCrossSigningDialog.js | 4 ++-- .../RestoreKeyBackupDialog.js | 0 .../dialogs/{ => security}/SetupEncryptionDialog.js | 12 ++++++------ src/components/views/rooms/RoomRecoveryReminder.js | 6 +++--- src/components/views/settings/ChangePassword.js | 2 +- src/components/views/settings/CrossSigningPanel.js | 2 +- src/components/views/settings/SecureBackupPanel.js | 4 ++-- .../settings/tabs/user/SecurityUserSettingsTab.js | 4 ++-- src/toasts/SetupEncryptionToast.ts | 2 +- .../views/dialogs/AccessSecretStorageDialog-test.js | 2 +- 28 files changed, 42 insertions(+), 43 deletions(-) rename res/css/views/dialogs/{secretstorage => security}/_AccessSecretStorageDialog.scss (100%) rename res/css/views/dialogs/{keybackup => security}/_CreateKeyBackupDialog.scss (100%) rename res/css/views/dialogs/{secretstorage => security}/_CreateSecretStorageDialog.scss (100%) rename res/css/views/dialogs/{keybackup => security}/_KeyBackupFailedDialog.scss (100%) rename res/css/views/dialogs/{keybackup => security}/_RestoreKeyBackupDialog.scss (100%) rename src/async-components/views/dialogs/{keybackup => security}/CreateKeyBackupDialog.js (100%) rename src/async-components/views/dialogs/{secretstorage => security}/CreateSecretStorageDialog.js (99%) rename src/async-components/views/dialogs/{ => security}/ExportE2eKeysDialog.js (97%) rename src/async-components/views/dialogs/{keybackup => security}/IgnoreRecoveryReminderDialog.js (100%) rename src/async-components/views/dialogs/{ => security}/ImportE2eKeysDialog.js (97%) rename src/async-components/views/dialogs/{keybackup => security}/NewRecoveryMethodDialog.js (97%) rename src/async-components/views/dialogs/{keybackup => security}/RecoveryMethodRemovedDialog.js (100%) rename src/components/views/dialogs/{secretstorage => security}/AccessSecretStorageDialog.js (100%) rename src/components/views/dialogs/{ => security}/ConfirmDestroyCrossSigningDialog.js (96%) rename src/components/views/dialogs/{keybackup => security}/RestoreKeyBackupDialog.js (100%) rename src/components/views/dialogs/{ => security}/SetupEncryptionDialog.js (80%) diff --git a/res/css/_components.scss b/res/css/_components.scss index 54e7436886..3263e3e28b 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -91,11 +91,11 @@ @import "./views/dialogs/_UploadConfirmDialog.scss"; @import "./views/dialogs/_UserSettingsDialog.scss"; @import "./views/dialogs/_WidgetOpenIDPermissionsDialog.scss"; -@import "./views/dialogs/keybackup/_CreateKeyBackupDialog.scss"; -@import "./views/dialogs/keybackup/_KeyBackupFailedDialog.scss"; -@import "./views/dialogs/keybackup/_RestoreKeyBackupDialog.scss"; -@import "./views/dialogs/secretstorage/_AccessSecretStorageDialog.scss"; -@import "./views/dialogs/secretstorage/_CreateSecretStorageDialog.scss"; +@import "./views/dialogs/security/_AccessSecretStorageDialog.scss"; +@import "./views/dialogs/security/_CreateKeyBackupDialog.scss"; +@import "./views/dialogs/security/_CreateSecretStorageDialog.scss"; +@import "./views/dialogs/security/_KeyBackupFailedDialog.scss"; +@import "./views/dialogs/security/_RestoreKeyBackupDialog.scss"; @import "./views/directory/_NetworkDropdown.scss"; @import "./views/elements/_AccessibleButton.scss"; @import "./views/elements/_AddressSelector.scss"; diff --git a/res/css/views/dialogs/secretstorage/_AccessSecretStorageDialog.scss b/res/css/views/dialogs/security/_AccessSecretStorageDialog.scss similarity index 100% rename from res/css/views/dialogs/secretstorage/_AccessSecretStorageDialog.scss rename to res/css/views/dialogs/security/_AccessSecretStorageDialog.scss diff --git a/res/css/views/dialogs/keybackup/_CreateKeyBackupDialog.scss b/res/css/views/dialogs/security/_CreateKeyBackupDialog.scss similarity index 100% rename from res/css/views/dialogs/keybackup/_CreateKeyBackupDialog.scss rename to res/css/views/dialogs/security/_CreateKeyBackupDialog.scss diff --git a/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss b/res/css/views/dialogs/security/_CreateSecretStorageDialog.scss similarity index 100% rename from res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss rename to res/css/views/dialogs/security/_CreateSecretStorageDialog.scss diff --git a/res/css/views/dialogs/keybackup/_KeyBackupFailedDialog.scss b/res/css/views/dialogs/security/_KeyBackupFailedDialog.scss similarity index 100% rename from res/css/views/dialogs/keybackup/_KeyBackupFailedDialog.scss rename to res/css/views/dialogs/security/_KeyBackupFailedDialog.scss diff --git a/res/css/views/dialogs/keybackup/_RestoreKeyBackupDialog.scss b/res/css/views/dialogs/security/_RestoreKeyBackupDialog.scss similarity index 100% rename from res/css/views/dialogs/keybackup/_RestoreKeyBackupDialog.scss rename to res/css/views/dialogs/security/_RestoreKeyBackupDialog.scss diff --git a/src/SecurityManager.js b/src/SecurityManager.js index cc7db3ead7..f6b9c993d0 100644 --- a/src/SecurityManager.js +++ b/src/SecurityManager.js @@ -22,6 +22,8 @@ import { decodeRecoveryKey } from 'matrix-js-sdk/src/crypto/recoverykey'; import { _t } from './languageHandler'; import {encodeBase64} from "matrix-js-sdk/src/crypto/olmlib"; import { isSecureBackupRequired } from './utils/WellKnownUtils'; +import AccessSecretStorageDialog from './components/views/dialogs/security/AccessSecretStorageDialog'; +import RestoreKeyBackupDialog from './components/views/dialogs/security/RestoreKeyBackupDialog'; // This stores the secret storage private keys in memory for the JS SDK. This is // only meant to act as a cache to avoid prompting the user multiple times @@ -87,8 +89,6 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) { return decodeRecoveryKey(recoveryKey); } }; - const AccessSecretStorageDialog = - sdk.getComponent("dialogs.secretstorage.AccessSecretStorageDialog"); const { finished } = Modal.createTrackedDialog("Access Secret Storage dialog", "", AccessSecretStorageDialog, /* props= */ @@ -181,7 +181,6 @@ export const crossSigningCallbacks = { export async function promptForBackupPassphrase() { let key; - const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog'); const { finished } = Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, { showSummary: false, keyCallback: k => key = k, }, null, /* priority = */ false, /* static = */ true); @@ -221,7 +220,7 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f // 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"), + import("./async-components/views/dialogs/security/CreateSecretStorageDialog"), { forceReset, }, diff --git a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js b/src/async-components/views/dialogs/security/CreateKeyBackupDialog.js similarity index 100% rename from src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js rename to src/async-components/views/dialogs/security/CreateKeyBackupDialog.js diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.js similarity index 99% rename from src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js rename to src/async-components/views/dialogs/security/CreateSecretStorageDialog.js index d4b1a73c3e..3908b7cd4a 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.js @@ -30,6 +30,7 @@ import StyledRadioButton from '../../../../components/views/elements/StyledRadio import AccessibleButton from "../../../../components/views/elements/AccessibleButton"; import DialogButtons from "../../../../components/views/elements/DialogButtons"; import InlineSpinner from "../../../../components/views/elements/InlineSpinner"; +import RestoreKeyBackupDialog from "../../../../components/views/dialogs/security/RestoreKeyBackupDialog"; import { isSecureBackupRequired } from '../../../../utils/WellKnownUtils'; const PHASE_LOADING = 0; @@ -341,7 +342,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent { // so let's stash it here, rather than prompting for it twice. const keyCallback = k => this._backupKey = k; - const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog'); const { finished } = Modal.createTrackedDialog( 'Restore Backup', '', RestoreKeyBackupDialog, { diff --git a/src/async-components/views/dialogs/ExportE2eKeysDialog.js b/src/async-components/views/dialogs/security/ExportE2eKeysDialog.js similarity index 97% rename from src/async-components/views/dialogs/ExportE2eKeysDialog.js rename to src/async-components/views/dialogs/security/ExportE2eKeysDialog.js index 406ffd8749..4dd296a8f1 100644 --- a/src/async-components/views/dialogs/ExportE2eKeysDialog.js +++ b/src/async-components/views/dialogs/security/ExportE2eKeysDialog.js @@ -17,11 +17,11 @@ limitations under the License. import FileSaver from 'file-saver'; import React, {createRef} from 'react'; import PropTypes from 'prop-types'; -import { _t } from '../../../languageHandler'; +import { _t } from '../../../../languageHandler'; import { MatrixClient } from 'matrix-js-sdk'; -import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption'; -import * as sdk from '../../../index'; +import * as MegolmExportEncryption from '../../../../utils/MegolmExportEncryption'; +import * as sdk from '../../../../index'; const PHASE_EDIT = 1; const PHASE_EXPORTING = 2; diff --git a/src/async-components/views/dialogs/keybackup/IgnoreRecoveryReminderDialog.js b/src/async-components/views/dialogs/security/IgnoreRecoveryReminderDialog.js similarity index 100% rename from src/async-components/views/dialogs/keybackup/IgnoreRecoveryReminderDialog.js rename to src/async-components/views/dialogs/security/IgnoreRecoveryReminderDialog.js diff --git a/src/async-components/views/dialogs/ImportE2eKeysDialog.js b/src/async-components/views/dialogs/security/ImportE2eKeysDialog.js similarity index 97% rename from src/async-components/views/dialogs/ImportE2eKeysDialog.js rename to src/async-components/views/dialogs/security/ImportE2eKeysDialog.js index c2d17f681d..e7bae3578b 100644 --- a/src/async-components/views/dialogs/ImportE2eKeysDialog.js +++ b/src/async-components/views/dialogs/security/ImportE2eKeysDialog.js @@ -18,9 +18,9 @@ import React, {createRef} from 'react'; import PropTypes from 'prop-types'; import { MatrixClient } from 'matrix-js-sdk'; -import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption'; -import * as sdk from '../../../index'; -import { _t } from '../../../languageHandler'; +import * as MegolmExportEncryption from '../../../../utils/MegolmExportEncryption'; +import * as sdk from '../../../../index'; +import { _t } from '../../../../languageHandler'; function readFileAsArrayBuffer(file) { return new Promise((resolve, reject) => { diff --git a/src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js b/src/async-components/views/dialogs/security/NewRecoveryMethodDialog.js similarity index 97% rename from src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js rename to src/async-components/views/dialogs/security/NewRecoveryMethodDialog.js index 74552a5c08..9f5045635d 100644 --- a/src/async-components/views/dialogs/keybackup/NewRecoveryMethodDialog.js +++ b/src/async-components/views/dialogs/security/NewRecoveryMethodDialog.js @@ -22,6 +22,7 @@ import {MatrixClientPeg} from '../../../../MatrixClientPeg'; import dis from "../../../../dispatcher/dispatcher"; import { _t } from "../../../../languageHandler"; import Modal from "../../../../Modal"; +import RestoreKeyBackupDialog from "../../../../components/views/dialogs/security/RestoreKeyBackupDialog"; import {Action} from "../../../../dispatcher/actions"; export default class NewRecoveryMethodDialog extends React.PureComponent { @@ -41,7 +42,6 @@ export default class NewRecoveryMethodDialog extends React.PureComponent { } onSetupClick = async () => { - const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog'); Modal.createTrackedDialog( 'Restore Backup', '', RestoreKeyBackupDialog, { onFinished: this.props.onFinished, diff --git a/src/async-components/views/dialogs/keybackup/RecoveryMethodRemovedDialog.js b/src/async-components/views/dialogs/security/RecoveryMethodRemovedDialog.js similarity index 100% rename from src/async-components/views/dialogs/keybackup/RecoveryMethodRemovedDialog.js rename to src/async-components/views/dialogs/security/RecoveryMethodRemovedDialog.js diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index dde5dc6fb2..95f60be86e 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1496,12 +1496,12 @@ export default class MatrixChat extends React.PureComponent { if (haveNewVersion) { Modal.createTrackedDialogAsync('New Recovery Method', 'New Recovery Method', - import('../../async-components/views/dialogs/keybackup/NewRecoveryMethodDialog'), + import('../../async-components/views/dialogs/security/NewRecoveryMethodDialog'), { newVersionInfo }, ); } else { Modal.createTrackedDialogAsync('Recovery Method Removed', 'Recovery Method Removed', - import('../../async-components/views/dialogs/keybackup/RecoveryMethodRemovedDialog'), + import('../../async-components/views/dialogs/security/RecoveryMethodRemovedDialog'), ); } }); diff --git a/src/components/structures/auth/E2eSetup.js b/src/components/structures/auth/E2eSetup.js index 9b390d24cc..91382d594d 100644 --- a/src/components/structures/auth/E2eSetup.js +++ b/src/components/structures/auth/E2eSetup.js @@ -29,7 +29,7 @@ export default class E2eSetup extends React.Component { super(); // awkwardly indented because https://github.com/eslint/eslint/issues/11310 this._createStorageDialogPromise = - import("../../../async-components/views/dialogs/secretstorage/CreateSecretStorageDialog"); + import("../../../async-components/views/dialogs/security/CreateSecretStorageDialog"); } render() { diff --git a/src/components/views/dialogs/LogoutDialog.js b/src/components/views/dialogs/LogoutDialog.js index 930acaa0b8..af36dba2b6 100644 --- a/src/components/views/dialogs/LogoutDialog.js +++ b/src/components/views/dialogs/LogoutDialog.js @@ -20,7 +20,8 @@ import Modal from '../../../Modal'; import * as sdk from '../../../index'; import dis from '../../../dispatcher/dispatcher'; import { _t } from '../../../languageHandler'; -import {MatrixClientPeg} from '../../../MatrixClientPeg'; +import { MatrixClientPeg } from '../../../MatrixClientPeg'; +import RestoreKeyBackupDialog from './security/RestoreKeyBackupDialog'; export default class LogoutDialog extends React.Component { defaultProps = { @@ -73,7 +74,7 @@ export default class LogoutDialog extends React.Component { _onExportE2eKeysClicked() { Modal.createTrackedDialogAsync('Export E2E Keys', '', - import('../../../async-components/views/dialogs/ExportE2eKeysDialog'), + import('../../../async-components/views/dialogs/security/ExportE2eKeysDialog'), { matrixClient: MatrixClientPeg.get(), }, @@ -93,14 +94,13 @@ export default class LogoutDialog extends React.Component { // A key backup exists for this account, but the creating device is not // verified, so restore the backup which will give us the keys from it and // allow us to trust it (ie. upload keys to it) - const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog'); Modal.createTrackedDialog( 'Restore Backup', '', RestoreKeyBackupDialog, null, null, /* priority = */ false, /* static = */ true, ); } else { Modal.createTrackedDialogAsync("Key Backup", "Key Backup", - import("../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog"), + import("../../../async-components/views/dialogs/security/CreateKeyBackupDialog"), null, null, /* priority = */ false, /* static = */ true, ); } diff --git a/src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js b/src/components/views/dialogs/security/AccessSecretStorageDialog.js similarity index 100% rename from src/components/views/dialogs/secretstorage/AccessSecretStorageDialog.js rename to src/components/views/dialogs/security/AccessSecretStorageDialog.js diff --git a/src/components/views/dialogs/ConfirmDestroyCrossSigningDialog.js b/src/components/views/dialogs/security/ConfirmDestroyCrossSigningDialog.js similarity index 96% rename from src/components/views/dialogs/ConfirmDestroyCrossSigningDialog.js rename to src/components/views/dialogs/security/ConfirmDestroyCrossSigningDialog.js index 9e1980e98d..abc1586205 100644 --- a/src/components/views/dialogs/ConfirmDestroyCrossSigningDialog.js +++ b/src/components/views/dialogs/security/ConfirmDestroyCrossSigningDialog.js @@ -16,8 +16,8 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import {_t} from "../../../languageHandler"; -import * as sdk from "../../../index"; +import {_t} from "../../../../languageHandler"; +import * as sdk from "../../../../index"; export default class ConfirmDestroyCrossSigningDialog extends React.Component { static propTypes = { diff --git a/src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js b/src/components/views/dialogs/security/RestoreKeyBackupDialog.js similarity index 100% rename from src/components/views/dialogs/keybackup/RestoreKeyBackupDialog.js rename to src/components/views/dialogs/security/RestoreKeyBackupDialog.js diff --git a/src/components/views/dialogs/SetupEncryptionDialog.js b/src/components/views/dialogs/security/SetupEncryptionDialog.js similarity index 80% rename from src/components/views/dialogs/SetupEncryptionDialog.js rename to src/components/views/dialogs/security/SetupEncryptionDialog.js index d7723de588..9ce3144534 100644 --- a/src/components/views/dialogs/SetupEncryptionDialog.js +++ b/src/components/views/dialogs/security/SetupEncryptionDialog.js @@ -16,16 +16,16 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import SetupEncryptionBody from '../../structures/auth/SetupEncryptionBody'; -import BaseDialog from './BaseDialog'; -import { _t } from '../../../languageHandler'; -import { SetupEncryptionStore, PHASE_DONE } from '../../../stores/SetupEncryptionStore'; +import SetupEncryptionBody from '../../../structures/auth/SetupEncryptionBody'; +import BaseDialog from '../BaseDialog'; +import { _t } from '../../../../languageHandler'; +import { SetupEncryptionStore, PHASE_DONE } from '../../../../stores/SetupEncryptionStore'; function iconFromPhase(phase) { if (phase === PHASE_DONE) { - return require("../../../../res/img/e2e/verified.svg"); + return require("../../../../../res/img/e2e/verified.svg"); } else { - return require("../../../../res/img/e2e/warning.svg"); + return require("../../../../../res/img/e2e/warning.svg"); } } diff --git a/src/components/views/rooms/RoomRecoveryReminder.js b/src/components/views/rooms/RoomRecoveryReminder.js index 859df6dd1b..552de681c3 100644 --- a/src/components/views/rooms/RoomRecoveryReminder.js +++ b/src/components/views/rooms/RoomRecoveryReminder.js @@ -23,6 +23,7 @@ import Modal from "../../../Modal"; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import SettingsStore from "../../../settings/SettingsStore"; import {SettingLevel} from "../../../settings/SettingLevel"; +import RestoreKeyBackupDialog from "../dialogs/security/RestoreKeyBackupDialog"; export default class RoomRecoveryReminder extends React.PureComponent { static propTypes = { @@ -70,14 +71,13 @@ export default class RoomRecoveryReminder extends React.PureComponent { // A key backup exists for this account, but the creating device is not // verified, so restore the backup which will give us the keys from it and // allow us to trust it (ie. upload keys to it) - const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog'); Modal.createTrackedDialog( 'Restore Backup', '', RestoreKeyBackupDialog, null, null, /* priority = */ false, /* static = */ true, ); } else { Modal.createTrackedDialogAsync("Key Backup", "Key Backup", - import("../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog"), + import("../../../async-components/views/dialogs/security/CreateKeyBackupDialog"), null, null, /* priority = */ false, /* static = */ true, ); } @@ -91,7 +91,7 @@ export default class RoomRecoveryReminder extends React.PureComponent { // When you choose "Don't ask again" from the room reminder, we show a // dialog to confirm the choice. Modal.createTrackedDialogAsync("Ignore Recovery Reminder", "Ignore Recovery Reminder", - import("../../../async-components/views/dialogs/keybackup/IgnoreRecoveryReminderDialog"), + import("../../../async-components/views/dialogs/security/IgnoreRecoveryReminderDialog"), { onDontAskAgain: async () => { await SettingsStore.setValue( diff --git a/src/components/views/settings/ChangePassword.js b/src/components/views/settings/ChangePassword.js index 725f04dede..0b62f1fa81 100644 --- a/src/components/views/settings/ChangePassword.js +++ b/src/components/views/settings/ChangePassword.js @@ -184,7 +184,7 @@ export default class ChangePassword extends React.Component { _onExportE2eKeysClicked = () => { Modal.createTrackedDialogAsync('Export E2E Keys', 'Change Password', - import('../../../async-components/views/dialogs/ExportE2eKeysDialog'), + import('../../../async-components/views/dialogs/security/ExportE2eKeysDialog'), { matrixClient: MatrixClientPeg.get(), }, diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index a0ca84645f..fd8fef0544 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -22,6 +22,7 @@ import * as sdk from '../../../index'; import Modal from '../../../Modal'; import Spinner from '../elements/Spinner'; import InteractiveAuthDialog from '../dialogs/InteractiveAuthDialog'; +import ConfirmDestroyCrossSigningDialog from '../dialogs/security/ConfirmDestroyCrossSigningDialog'; export default class CrossSigningPanel extends React.PureComponent { constructor(props) { @@ -137,7 +138,6 @@ export default class CrossSigningPanel extends React.PureComponent { } _resetCrossSigning = () => { - const ConfirmDestroyCrossSigningDialog = sdk.getComponent("dialogs.ConfirmDestroyCrossSigningDialog"); Modal.createDialog(ConfirmDestroyCrossSigningDialog, { onFinished: (act) => { if (!act) return; diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js index 7f0655d54a..f94a4c9590 100644 --- a/src/components/views/settings/SecureBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -24,7 +24,7 @@ import { isSecureBackupRequired } from '../../../utils/WellKnownUtils'; import Spinner from '../elements/Spinner'; import AccessibleButton from '../elements/AccessibleButton'; import QuestionDialog from '../dialogs/QuestionDialog'; -import RestoreKeyBackupDialog from '../dialogs/keybackup/RestoreKeyBackupDialog'; +import RestoreKeyBackupDialog from '../dialogs/security/RestoreKeyBackupDialog'; import { accessSecretStorage } from '../../../SecurityManager'; export default class SecureBackupPanel extends React.PureComponent { @@ -150,7 +150,7 @@ export default class SecureBackupPanel extends React.PureComponent { _startNewBackup = () => { Modal.createTrackedDialogAsync('Key Backup', 'Key Backup', - import('../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog'), + import('../../../async-components/views/dialogs/security/CreateKeyBackupDialog'), { onFinished: () => { this._loadBackupStatus(); diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js index c25ac438e0..de03360f2a 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js @@ -103,14 +103,14 @@ export default class SecurityUserSettingsTab extends React.Component { _onExportE2eKeysClicked = () => { Modal.createTrackedDialogAsync('Export E2E Keys', '', - import('../../../../../async-components/views/dialogs/ExportE2eKeysDialog'), + import('../../../../../async-components/views/dialogs/security/ExportE2eKeysDialog'), {matrixClient: MatrixClientPeg.get()}, ); }; _onImportE2eKeysClicked = () => { Modal.createTrackedDialogAsync('Import E2E Keys', '', - import('../../../../../async-components/views/dialogs/ImportE2eKeysDialog'), + import('../../../../../async-components/views/dialogs/security/ImportE2eKeysDialog'), {matrixClient: MatrixClientPeg.get()}, ); }; diff --git a/src/toasts/SetupEncryptionToast.ts b/src/toasts/SetupEncryptionToast.ts index 9dbc4acafc..5e3da94eda 100644 --- a/src/toasts/SetupEncryptionToast.ts +++ b/src/toasts/SetupEncryptionToast.ts @@ -18,7 +18,7 @@ import Modal from "../Modal"; import * as sdk from "../index"; import { _t } from "../languageHandler"; import DeviceListener from "../DeviceListener"; -import SetupEncryptionDialog from "../components/views/dialogs/SetupEncryptionDialog"; +import SetupEncryptionDialog from "../components/views/dialogs/security/SetupEncryptionDialog"; import { accessSecretStorage } from "../SecurityManager"; import ToastStore from "../stores/ToastStore"; import GenericToast from "../components/views/toasts/GenericToast"; diff --git a/test/components/views/dialogs/AccessSecretStorageDialog-test.js b/test/components/views/dialogs/AccessSecretStorageDialog-test.js index 5a8dcbf763..7c4b2996c9 100644 --- a/test/components/views/dialogs/AccessSecretStorageDialog-test.js +++ b/test/components/views/dialogs/AccessSecretStorageDialog-test.js @@ -20,7 +20,7 @@ import sdk from '../../../skinned-sdk'; import {MatrixClientPeg} from '../../../../src/MatrixClientPeg'; import { stubClient } from '../../../test-utils'; -const AccessSecretStorageDialog = sdk.getComponent("dialogs.secretstorage.AccessSecretStorageDialog"); +const AccessSecretStorageDialog = sdk.getComponent("dialogs.security.AccessSecretStorageDialog"); describe("AccessSecretStorageDialog", function() { it("Closes the dialog if _onRecoveryKeyNext is called with a valid key", (done) => { From 3259ab1f250ee0d027a4a5d020f360848a6562db Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 11 Sep 2020 14:09:54 +0100 Subject: [PATCH 245/286] Place cross-signing action buttons on a single row Part of https://github.com/vector-im/element-web/issues/13895 --- .../views/settings/_CrossSigningPanel.scss | 4 ++ .../views/settings/CrossSigningPanel.js | 40 ++++++++++--------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/res/css/views/settings/_CrossSigningPanel.scss b/res/css/views/settings/_CrossSigningPanel.scss index fa9f76a963..12a0e36835 100644 --- a/res/css/views/settings/_CrossSigningPanel.scss +++ b/res/css/views/settings/_CrossSigningPanel.scss @@ -28,4 +28,8 @@ limitations under the License. .mx_CrossSigningPanel_buttonRow { margin: 1em 0; + + :nth-child(n + 1) { + margin-inline-end: 10px; + } } diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index fd8fef0544..5b5ef56024 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -195,29 +195,32 @@ export default class CrossSigningPanel extends React.PureComponent { crossSigningPublicKeysOnDevice ); - let resetButton; - if (keysExistAnywhere) { - resetButton = ( -
    - - {_t("Reset")} - -
    + const actions = []; + + // TODO: determine how better to expose this to users in addition to prompts at login/toast + if (!keysExistEverywhere && homeserverSupportsCrossSigning) { + actions.push( + + {_t("Set up")} + , ); } - // TODO: determine how better to expose this to users in addition to prompts at login/toast - let bootstrapButton; - if (!keysExistEverywhere && homeserverSupportsCrossSigning) { - bootstrapButton = ( -
    - - {_t("Set up")} - -
    + if (keysExistAnywhere) { + actions.push( + + {_t("Reset")} + , ); } + let actionRow; + if (actions.length) { + actionRow =
    + {actions} +
    ; + } + return (
    {summarisedStatus} @@ -251,8 +254,7 @@ export default class CrossSigningPanel extends React.PureComponent { {errorSection} - {bootstrapButton} - {resetButton} + {actionRow}
    ); } From 685878a10189043ca09b39daab04ce40a22b7c53 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 11 Sep 2020 14:20:08 +0100 Subject: [PATCH 246/286] Clarify diagnostic about keys in storage Part of https://github.com/vector-im/element-web/issues/13895 --- src/components/views/settings/CrossSigningPanel.js | 2 +- src/i18n/strings/en_EN.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index 5b5ef56024..fd5966ca0a 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -233,7 +233,7 @@ export default class CrossSigningPanel extends React.PureComponent { {_t("Cross-signing private keys:")} - {crossSigningPrivateKeysInStorage ? _t("in secret storage") : _t("not found")} + {crossSigningPrivateKeysInStorage ? _t("in secret storage") : _t("not found in storage")} {_t("Master private key:")} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4414077005..76ca0bf738 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -658,6 +658,7 @@ "not found": "not found", "Cross-signing private keys:": "Cross-signing private keys:", "in secret storage": "in secret storage", + "not found in storage": "not found in storage", "Master private key:": "Master private key:", "cached locally": "cached locally", "not found locally": "not found locally", From 7be27e70c964329769d9ceab8185824d5ea975da Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 15 Sep 2020 12:45:08 +0100 Subject: [PATCH 247/286] Add component key to actions array --- src/components/views/settings/CrossSigningPanel.js | 4 ++-- src/components/views/settings/SecureBackupPanel.js | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index fd5966ca0a..669c2e84d9 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -200,7 +200,7 @@ export default class CrossSigningPanel extends React.PureComponent { // TODO: determine how better to expose this to users in addition to prompts at login/toast if (!keysExistEverywhere && homeserverSupportsCrossSigning) { actions.push( - + {_t("Set up")} , ); @@ -208,7 +208,7 @@ export default class CrossSigningPanel extends React.PureComponent { if (keysExistAnywhere) { actions.push( - + {_t("Reset")} , ); diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js index f94a4c9590..7e9fb6cd3d 100644 --- a/src/components/views/settings/SecureBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -367,14 +367,14 @@ export default class SecureBackupPanel extends React.PureComponent { ; actions.push( - + {restoreButtonCaption} , ); if (!isSecureBackupRequired()) { actions.push( - + {_t("Delete Backup")} , ); @@ -388,7 +388,7 @@ export default class SecureBackupPanel extends React.PureComponent {

    {_t("Back up your keys before signing out to avoid losing them.")}

    ; actions.push( - + {_t("Set up")} , ); @@ -396,7 +396,7 @@ export default class SecureBackupPanel extends React.PureComponent { if (secretStorageKeyInAccount) { actions.push( - + {_t("Reset")} , ); From 46f37fb969798069e4e7d1040494275e9e7b6af9 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 15 Sep 2020 15:25:50 +0100 Subject: [PATCH 248/286] Create cross-signing keys during authentication With this change, Element now creates cross-signing keys during auth flows for password login. For other auth flows like token / SSO, it will not happen until a cross-signing / secret storage dialog flow as before. --- .../security/_CreateCrossSigningDialog.scss | 33 ++++ .../security/CreateSecretStorageDialog.js | 16 +- src/components/structures/MatrixChat.tsx | 7 + src/components/structures/auth/E2eSetup.js | 17 +- .../security/CreateCrossSigningDialog.js | 187 ++++++++++++++++++ src/i18n/strings/en_EN.json | 101 +++++----- test/end-to-end-tests/src/usecases/signup.js | 15 -- 7 files changed, 290 insertions(+), 86 deletions(-) create mode 100644 res/css/views/dialogs/security/_CreateCrossSigningDialog.scss create mode 100644 src/components/views/dialogs/security/CreateCrossSigningDialog.js diff --git a/res/css/views/dialogs/security/_CreateCrossSigningDialog.scss b/res/css/views/dialogs/security/_CreateCrossSigningDialog.scss new file mode 100644 index 0000000000..8303e02b9e --- /dev/null +++ b/res/css/views/dialogs/security/_CreateCrossSigningDialog.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_CreateCrossSigningDialog { + // Why you ask? Because CompleteSecurityBody is 600px so this is the width + // we end up when in there, but when in our own dialog we set our own width + // so need to fix it to something sensible as otherwise we'd end up either + // really wide or really narrow depending on the phase. I bet you wish you + // never asked. + width: 560px; + + details .mx_AccessibleButton { + margin: 1em 0; // emulate paragraph spacing because we can't put this button in a paragraph due to HTML rules + } +} + +.mx_CreateCrossSigningDialog .mx_Dialog_title { + /* TODO: Consider setting this for all dialog titles. */ + margin-bottom: 1em; +} diff --git a/src/async-components/views/dialogs/security/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.js index 3908b7cd4a..f3b52da141 100644 --- a/src/async-components/views/dialogs/security/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.js @@ -281,21 +281,21 @@ export default class CreateSecretStorageDialog extends React.PureComponent { const { forceReset } = this.props; try { - // JRS: In an upcoming change, the cross-signing steps will be - // removed from here and this will instead be about secret storage - // only. if (forceReset) { - console.log("Forcing cross-signing and secret storage reset"); + console.log("Forcing secret storage reset"); await cli.bootstrapSecretStorage({ createSecretStorageKey: async () => this._recoveryKey, setupNewKeyBackup: true, setupNewSecretStorage: true, }); - await cli.bootstrapCrossSigning({ - authUploadDeviceSigningKeys: this._doBootstrapUIAuth, - setupNewCrossSigning: true, - }); } else { + // For password authentication users after 2020-09, this cross-signing + // step will be a no-op since it is now setup during registration or login + // when needed. We should keep this here to cover other cases such as: + // * Users with existing sessions prior to 2020-09 changes + // * SSO authentication users which require interactive auth to upload + // keys (and also happen to skip all post-authentication flows at the + // moment via token login) await cli.bootstrapCrossSigning({ authUploadDeviceSigningKeys: this._doBootstrapUIAuth, }); diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 95f60be86e..c4d4a82a82 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1876,6 +1876,13 @@ export default class MatrixChat extends React.PureComponent { return this.props.makeRegistrationUrl(params); }; + /** + * After registration or login, we run various post-auth steps before entering the app + * proper, such setting up cross-signing or verifying the new session. + * + * Note: SSO users (and any others using token login) currently do not pass through + * this, as they instead jump straight into the app after `attemptTokenLogin`. + */ onUserCompletedLoginFlow = async (credentials: object, password: string) => { this.accountPassword = password; // self-destruct the password after 5mins diff --git a/src/components/structures/auth/E2eSetup.js b/src/components/structures/auth/E2eSetup.js index 91382d594d..6df8158002 100644 --- a/src/components/structures/auth/E2eSetup.js +++ b/src/components/structures/auth/E2eSetup.js @@ -16,8 +16,9 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import AsyncWrapper from '../../../AsyncWrapper'; -import * as sdk from '../../../index'; +import AuthPage from '../../views/auth/AuthPage'; +import CompleteSecurityBody from '../../views/auth/CompleteSecurityBody'; +import CreateCrossSigningDialog from '../../views/dialogs/security/CreateCrossSigningDialog'; export default class E2eSetup extends React.Component { static propTypes = { @@ -25,21 +26,11 @@ export default class E2eSetup extends React.Component { accountPassword: PropTypes.string, }; - constructor() { - super(); - // awkwardly indented because https://github.com/eslint/eslint/issues/11310 - this._createStorageDialogPromise = - import("../../../async-components/views/dialogs/security/CreateSecretStorageDialog"); - } - render() { - const AuthPage = sdk.getComponent("auth.AuthPage"); - const CompleteSecurityBody = sdk.getComponent("auth.CompleteSecurityBody"); return ( - diff --git a/src/components/views/dialogs/security/CreateCrossSigningDialog.js b/src/components/views/dialogs/security/CreateCrossSigningDialog.js new file mode 100644 index 0000000000..226419e759 --- /dev/null +++ b/src/components/views/dialogs/security/CreateCrossSigningDialog.js @@ -0,0 +1,187 @@ +/* +Copyright 2018, 2019 New Vector Ltd +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. +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 { MatrixClientPeg } from '../../../../MatrixClientPeg'; +import { _t } from '../../../../languageHandler'; +import Modal from '../../../../Modal'; +import { SSOAuthEntry } from '../../auth/InteractiveAuthEntryComponents'; +import DialogButtons from '../../elements/DialogButtons'; +import BaseDialog from '../BaseDialog'; +import Spinner from '../../elements/Spinner'; +import InteractiveAuthDialog from '../InteractiveAuthDialog'; + +/* + * Walks the user through the process of creating a cross-signing keys. In most + * cases, only a spinner is shown, but for more complex auth like SSO, the user + * may need to complete some steps to proceed. + */ +export default class CreateCrossSigningDialog extends React.PureComponent { + static propTypes = { + accountPassword: PropTypes.string, + }; + + constructor(props) { + super(props); + + this.state = { + error: null, + // Does the server offer a UI auth flow with just m.login.password + // for /keys/device_signing/upload? + canUploadKeysWithPasswordOnly: null, + accountPassword: props.accountPassword || "", + }; + + 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(); + } + } + + componentDidMount() { + this._bootstrapCrossSigning(); + } + + async _queryKeyUploadAuth() { + try { + 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!"); + } catch (error) { + if (!error.data || !error.data.flows) { + console.log("uploadDeviceSigningKeys advertised no flows!"); + return; + } + const canUploadKeysWithPasswordOnly = error.data.flows.some(f => { + return f.stages.length === 1 && f.stages[0] === 'm.login.password'; + }); + this.setState({ + canUploadKeysWithPasswordOnly, + }); + } + } + + _doBootstrapUIAuth = async (makeRequest) => { + if (this.state.canUploadKeysWithPasswordOnly && this.state.accountPassword) { + await makeRequest({ + type: 'm.login.password', + identifier: { + type: 'm.id.user', + user: MatrixClientPeg.get().getUserId(), + }, + // TODO: Remove `user` once servers support proper UIA + // See https://github.com/matrix-org/synapse/issues/5665 + user: MatrixClientPeg.get().getUserId(), + password: this.state.accountPassword, + }); + } else { + const dialogAesthetics = { + [SSOAuthEntry.PHASE_PREAUTH]: { + title: _t("Use Single Sign On to continue"), + body: _t("To continue, use Single Sign On to prove your identity."), + continueText: _t("Single Sign On"), + continueKind: "primary", + }, + [SSOAuthEntry.PHASE_POSTAUTH]: { + title: _t("Confirm encryption setup"), + body: _t("Click the button below to confirm setting up encryption."), + continueText: _t("Confirm"), + continueKind: "primary", + }, + }; + + const { finished } = Modal.createTrackedDialog( + 'Cross-signing keys dialog', '', InteractiveAuthDialog, + { + title: _t("Setting up keys"), + matrixClient: MatrixClientPeg.get(), + makeRequest, + aestheticsForStagePhases: { + [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics, + [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics, + }, + }, + ); + const [confirmed] = await finished; + if (!confirmed) { + throw new Error("Cross-signing key upload auth canceled"); + } + } + } + + _bootstrapCrossSigning = async () => { + this.setState({ + error: null, + }); + + const cli = MatrixClientPeg.get(); + + try { + await cli.bootstrapCrossSigning({ + authUploadDeviceSigningKeys: this._doBootstrapUIAuth, + }); + this.props.onFinished(true); + } catch (e) { + this.setState({ error: e }); + console.error("Error bootstrapping cross-signing", e); + } + } + + _onCancel = () => { + this.props.onFinished(false); + } + + render() { + let content; + if (this.state.error) { + content =
    +

    {_t("Unable to set up keys")}

    +
    + +
    +
    ; + } else { + content =
    + +
    ; + } + + return ( + +
    + {content} +
    +
    + ); + } +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 76ca0bf738..ea558fbd93 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1631,9 +1631,6 @@ "Invite people to join %(communityName)s": "Invite people to join %(communityName)s", "You cannot delete this message. (%(code)s)": "You cannot delete this message. (%(code)s)", "Removing…": "Removing…", - "Destroy cross-signing keys?": "Destroy cross-signing keys?", - "Deleting cross-signing keys is permanent. Anyone you have verified with will see security alerts. You almost certainly don't want to do this, unless you've lost every device you can cross-sign from.": "Deleting cross-signing keys is permanent. Anyone you have verified with will see security alerts. You almost certainly don't want to do this, unless you've lost every device you can cross-sign from.", - "Clear cross-signing keys": "Clear cross-signing keys", "Confirm Removal": "Confirm Removal", "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.": "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.", "Clear all data in this session?": "Clear all data in this session?", @@ -1886,6 +1883,13 @@ "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.", + "Destroy cross-signing keys?": "Destroy cross-signing keys?", + "Deleting cross-signing keys is permanent. Anyone you have verified with will see security alerts. You almost certainly don't want to do this, unless you've lost every device you can cross-sign from.": "Deleting cross-signing keys is permanent. Anyone you have verified with will see security alerts. You almost certainly don't want to do this, unless you've lost every device you can cross-sign from.", + "Clear cross-signing keys": "Clear cross-signing keys", + "Confirm encryption setup": "Confirm encryption setup", + "Click the button below to confirm setting up encryption.": "Click the button below to confirm setting up encryption.", + "Unable to set up keys": "Unable to set up keys", + "Retry": "Retry", "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", @@ -2235,22 +2239,36 @@ "Room Autocomplete": "Room Autocomplete", "Users": "Users", "User Autocomplete": "User Autocomplete", - "Passphrases must match": "Passphrases must match", - "Passphrase must not be empty": "Passphrase must not be empty", - "Unknown error": "Unknown error", - "Export room keys": "Export room keys", - "This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.": "This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.", - "The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.": "The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.", - "Enter passphrase": "Enter passphrase", - "Confirm passphrase": "Confirm passphrase", - "Export": "Export", - "Import room keys": "Import room keys", - "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.": "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.", - "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.", - "File to import": "File to import", - "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.", + "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.", + "Enter a recovery passphrase": "Enter a recovery passphrase", + "Great! This recovery passphrase looks strong enough.": "Great! This recovery passphrase looks strong enough.", + "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...", + "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", + "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", + "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", + "Unable to create key backup": "Unable to create key backup", "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.", @@ -2262,18 +2280,9 @@ "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.", "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.", - "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.", "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", "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", "Unable to query secret storage status": "Unable to query secret storage status", - "Retry": "Retry", "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", @@ -2282,31 +2291,23 @@ "Confirm Security Phrase": "Confirm Security Phrase", "Save your Security Key": "Save your Security 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.", - "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.", - "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", - "Unable to create key backup": "Unable to create key backup", + "Passphrases must match": "Passphrases must match", + "Passphrase must not be empty": "Passphrase must not be empty", + "Unknown error": "Unknown error", + "Export room keys": "Export room keys", + "This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.": "This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.", + "The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.": "The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.", + "Enter passphrase": "Enter passphrase", + "Confirm passphrase": "Confirm passphrase", + "Export": "Export", "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.": "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.", "If you don't want to set this up now, you can later in Settings.": "If you don't want to set this up now, you can later in Settings.", "Don't ask again": "Don't ask again", + "Import room keys": "Import room keys", + "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.": "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.", + "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.", + "File to import": "File to import", + "Import": "Import", "New Recovery Method": "New Recovery Method", "A new recovery passphrase and key for Secure Messages have been detected.": "A new recovery passphrase and key for Secure Messages have been detected.", "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.": "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.", diff --git a/test/end-to-end-tests/src/usecases/signup.js b/test/end-to-end-tests/src/usecases/signup.js index fd41ef1a71..ef8a259091 100644 --- a/test/end-to-end-tests/src/usecases/signup.js +++ b/test/end-to-end-tests/src/usecases/signup.js @@ -79,21 +79,6 @@ module.exports = async function signup(session, username, password, homeserver) const acceptButton = await session.query('.mx_InteractiveAuthEntryComponents_termsSubmit'); await acceptButton.click(); - // Continue with the default (generate a security key) - const 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_buttons .mx_Dialog_primary', - ); - await copyContinueButton.click(); - //wait for registration to finish so the hash gets set //onhashchange better? From ada00a3535833ee7687c0ce6696d1ae7d45a028c Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 15 Sep 2020 17:37:05 +0100 Subject: [PATCH 249/286] Recheck security status on room encryption change This ensures we are alerted when you first interact with an encrypted room. Part of https://github.com/vector-im/element-web/issues/13895 --- src/DeviceListener.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/DeviceListener.ts b/src/DeviceListener.ts index aa0508924d..89cf968c6b 100644 --- a/src/DeviceListener.ts +++ b/src/DeviceListener.ts @@ -33,7 +33,7 @@ import { privateShouldBeEncrypted } from "./createRoom"; import { isSecretStorageBeingAccessed, accessSecretStorage } from "./SecurityManager"; import { isSecureBackupRequired } from './utils/WellKnownUtils'; import { isLoggedIn } from './components/structures/MatrixChat'; - +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000; @@ -66,6 +66,7 @@ export default class DeviceListener { MatrixClientPeg.get().on('crossSigning.keysChanged', this._onCrossSingingKeysChanged); MatrixClientPeg.get().on('accountData', this._onAccountData); MatrixClientPeg.get().on('sync', this._onSync); + MatrixClientPeg.get().on('RoomState.events', this._onRoomStateEvents); this.dispatcherRef = dis.register(this._onAction); this._recheck(); } @@ -79,6 +80,7 @@ export default class DeviceListener { MatrixClientPeg.get().removeListener('crossSigning.keysChanged', this._onCrossSingingKeysChanged); MatrixClientPeg.get().removeListener('accountData', this._onAccountData); MatrixClientPeg.get().removeListener('sync', this._onSync); + MatrixClientPeg.get().removeListener('RoomState.events', this._onRoomStateEvents); } if (this.dispatcherRef) { dis.unregister(this.dispatcherRef); @@ -169,6 +171,16 @@ export default class DeviceListener { if (state === 'PREPARED' && prevState === null) this._recheck(); }; + _onRoomStateEvents = (ev: MatrixEvent) => { + if (ev.getType() !== "m.room.encryption") { + return; + } + + // If a room changes to encrypted, re-check as it may be our first + // encrypted room. This also catches encrypted room creation as well. + this._recheck(); + }; + _onAction = ({ action }) => { if (action !== "on_logged_in") return; this._recheck(); From 26b465f1fd537d45ad5bfd3f0320157d73c74e1d Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 15 Sep 2020 17:53:04 +0100 Subject: [PATCH 250/286] Remove room recovery reminder The Secure Backup toast replaces this with better UX. Part of https://github.com/vector-im/element-web/issues/13895 --- res/css/_components.scss | 2 +- .../views/rooms/_RoomRecoveryReminder.scss | 39 ---- .../security/IgnoreRecoveryReminderDialog.js | 70 -------- src/components/structures/RoomView.tsx | 17 -- .../views/rooms/RoomRecoveryReminder.js | 170 ------------------ src/i18n/strings/en_EN.json | 11 +- src/settings/Settings.ts | 5 - 7 files changed, 2 insertions(+), 312 deletions(-) delete mode 100644 res/css/views/rooms/_RoomRecoveryReminder.scss delete mode 100644 src/async-components/views/dialogs/security/IgnoreRecoveryReminderDialog.js delete mode 100644 src/components/views/rooms/RoomRecoveryReminder.js diff --git a/res/css/_components.scss b/res/css/_components.scss index 3263e3e28b..35b4c1b965 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -92,6 +92,7 @@ @import "./views/dialogs/_UserSettingsDialog.scss"; @import "./views/dialogs/_WidgetOpenIDPermissionsDialog.scss"; @import "./views/dialogs/security/_AccessSecretStorageDialog.scss"; +@import "./views/dialogs/security/_CreateCrossSigningDialog.scss"; @import "./views/dialogs/security/_CreateKeyBackupDialog.scss"; @import "./views/dialogs/security/_CreateSecretStorageDialog.scss"; @import "./views/dialogs/security/_KeyBackupFailedDialog.scss"; @@ -187,7 +188,6 @@ @import "./views/rooms/_RoomHeader.scss"; @import "./views/rooms/_RoomList.scss"; @import "./views/rooms/_RoomPreviewBar.scss"; -@import "./views/rooms/_RoomRecoveryReminder.scss"; @import "./views/rooms/_RoomSublist.scss"; @import "./views/rooms/_RoomTile.scss"; @import "./views/rooms/_RoomUpgradeWarningBar.scss"; diff --git a/res/css/views/rooms/_RoomRecoveryReminder.scss b/res/css/views/rooms/_RoomRecoveryReminder.scss deleted file mode 100644 index 09b28ae235..0000000000 --- a/res/css/views/rooms/_RoomRecoveryReminder.scss +++ /dev/null @@ -1,39 +0,0 @@ -/* -Copyright 2018 New Vector 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. -*/ - -.mx_RoomRecoveryReminder { - display: flex; - flex-direction: column; - text-align: center; - background-color: $room-warning-bg-color; - padding: 20px; - border: 1px solid $primary-hairline-color; - border-bottom: unset; -} - -.mx_RoomRecoveryReminder_header { - font-weight: bold; - margin-bottom: 1em; -} - -.mx_RoomRecoveryReminder_body { - margin-bottom: 1em; -} - -.mx_RoomRecoveryReminder_secondary { - font-size: 90%; - margin-top: 1em; -} diff --git a/src/async-components/views/dialogs/security/IgnoreRecoveryReminderDialog.js b/src/async-components/views/dialogs/security/IgnoreRecoveryReminderDialog.js deleted file mode 100644 index b79911c66e..0000000000 --- a/src/async-components/views/dialogs/security/IgnoreRecoveryReminderDialog.js +++ /dev/null @@ -1,70 +0,0 @@ -/* -Copyright 2018 New Vector 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 PropTypes from "prop-types"; -import * as sdk from "../../../../index"; -import { _t } from "../../../../languageHandler"; - -export default class IgnoreRecoveryReminderDialog extends React.PureComponent { - static propTypes = { - onDontAskAgain: PropTypes.func.isRequired, - onFinished: PropTypes.func.isRequired, - onSetup: PropTypes.func.isRequired, - } - - onDontAskAgainClick = () => { - this.props.onFinished(); - this.props.onDontAskAgain(); - } - - onSetupClick = () => { - this.props.onFinished(); - this.props.onSetup(); - } - - render() { - const BaseDialog = sdk.getComponent("views.dialogs.BaseDialog"); - const DialogButtons = sdk.getComponent("views.elements.DialogButtons"); - - return ( - -
    -

    {_t( - "Without setting up Secure Message Recovery, " + - "you'll lose your secure message history when you " + - "log out.", - )}

    -

    {_t( - "If you don't want to set this up now, you can later " + - "in Settings.", - )}

    -
    - -
    -
    -
    - ); - } -} diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 039d36a8de..f568f31dbd 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -65,7 +65,6 @@ import RoomPreviewBar from "../views/rooms/RoomPreviewBar"; import ForwardMessage from "../views/rooms/ForwardMessage"; import SearchBar from "../views/rooms/SearchBar"; import RoomUpgradeWarningBar from "../views/rooms/RoomUpgradeWarningBar"; -import RoomRecoveryReminder from "../views/rooms/RoomRecoveryReminder"; import PinnedEventsPanel from "../views/rooms/PinnedEventsPanel"; import AuxPanel from "../views/rooms/AuxPanel"; import RoomHeader from "../views/rooms/RoomHeader"; @@ -816,12 +815,6 @@ export default class RoomView extends React.Component { } }; - private onRoomRecoveryReminderDontAskAgain = () => { - // Called when the option to not ask again is set: - // force an update to hide the recovery reminder - this.forceUpdate(); - }; - private onKeyBackupStatus = () => { // Key backup status changes affect whether the in-room recovery // reminder is displayed. @@ -1858,13 +1851,6 @@ export default class RoomView extends React.Component { this.state.room.userMayUpgradeRoom(this.context.credentials.userId) ); - const showRoomRecoveryReminder = ( - this.context.isCryptoEnabled() && - SettingsStore.getValue("showRoomRecoveryReminder") && - this.context.isRoomEncrypted(this.state.room.roomId) && - this.context.getKeyBackupEnabled() === false - ); - const hiddenHighlightCount = this.getHiddenHighlightCount(); let aux = null; @@ -1883,9 +1869,6 @@ export default class RoomView extends React.Component { } else if (showRoomUpgradeBar) { aux = ; hideCancel = true; - } else if (showRoomRecoveryReminder) { - aux = ; - hideCancel = true; } else if (this.state.showingPinned) { hideCancel = true; // has own cancel aux = ; diff --git a/src/components/views/rooms/RoomRecoveryReminder.js b/src/components/views/rooms/RoomRecoveryReminder.js deleted file mode 100644 index 552de681c3..0000000000 --- a/src/components/views/rooms/RoomRecoveryReminder.js +++ /dev/null @@ -1,170 +0,0 @@ -/* -Copyright 2018, 2019 New Vector 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. -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 * as sdk from "../../../index"; -import { _t } from "../../../languageHandler"; -import Modal from "../../../Modal"; -import {MatrixClientPeg} from "../../../MatrixClientPeg"; -import SettingsStore from "../../../settings/SettingsStore"; -import {SettingLevel} from "../../../settings/SettingLevel"; -import RestoreKeyBackupDialog from "../dialogs/security/RestoreKeyBackupDialog"; - -export default class RoomRecoveryReminder extends React.PureComponent { - static propTypes = { - // called if the user sets the option to suppress this reminder in the future - onDontAskAgainSet: PropTypes.func, - } - - static defaultProps = { - onDontAskAgainSet: function() {}, - } - - constructor(props) { - super(props); - - this.state = { - loading: true, - error: null, - backupInfo: null, - notNowClicked: false, - }; - } - - componentDidMount() { - this._loadBackupStatus(); - } - - async _loadBackupStatus() { - try { - const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion(); - this.setState({ - loading: false, - backupInfo, - }); - } catch (e) { - console.log("Unable to fetch key backup status", e); - this.setState({ - loading: false, - error: e, - }); - } - } - - showSetupDialog = () => { - if (this.state.backupInfo) { - // A key backup exists for this account, but the creating device is not - // verified, so restore the backup which will give us the keys from it and - // allow us to trust it (ie. upload keys to it) - Modal.createTrackedDialog( - 'Restore Backup', '', RestoreKeyBackupDialog, null, null, - /* priority = */ false, /* static = */ true, - ); - } else { - Modal.createTrackedDialogAsync("Key Backup", "Key Backup", - import("../../../async-components/views/dialogs/security/CreateKeyBackupDialog"), - null, null, /* priority = */ false, /* static = */ true, - ); - } - } - - onOnNotNowClick = () => { - this.setState({notNowClicked: true}); - } - - onDontAskAgainClick = () => { - // When you choose "Don't ask again" from the room reminder, we show a - // dialog to confirm the choice. - Modal.createTrackedDialogAsync("Ignore Recovery Reminder", "Ignore Recovery Reminder", - import("../../../async-components/views/dialogs/security/IgnoreRecoveryReminderDialog"), - { - onDontAskAgain: async () => { - await SettingsStore.setValue( - "showRoomRecoveryReminder", - null, - SettingLevel.ACCOUNT, - false, - ); - this.props.onDontAskAgainSet(); - }, - onSetup: () => { - this.showSetupDialog(); - }, - }, - ); - } - - onSetupClick = () => { - this.showSetupDialog(); - } - - render() { - // If there was an error loading just don't display the banner: we'll try again - // next time the user switchs to the room. - if (this.state.error || this.state.loading || this.state.notNowClicked) { - return null; - } - - const AccessibleButton = sdk.getComponent("views.elements.AccessibleButton"); - - let setupCaption; - if (this.state.backupInfo) { - setupCaption = _t("Connect this session to Key Backup"); - } else { - setupCaption = _t("Start using Key Backup"); - } - - return ( -
    -
    {_t( - "Never lose encrypted messages", - )}
    -
    -

    {_t( - "Messages in this room are secured with end-to-end " + - "encryption. Only you and the recipient(s) have the " + - "keys to read these messages.", - )}

    -

    {_t( - "Securely back up your keys to avoid losing them. " + - "Learn more.", {}, - { - // TODO: We don't have this link yet: this will prevent the translators - // having to re-translate the string when we do. - a: sub => '', - }, - )}

    -
    -
    - - {setupCaption} - - - { _t("Not now") } - - - { _t("Don't ask me again") } - -
    -
    - ); - } -} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index ea558fbd93..727898f1a0 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -474,7 +474,6 @@ "Show timestamps in 12 hour format (e.g. 2:30pm)": "Show timestamps in 12 hour format (e.g. 2:30pm)", "Always show message timestamps": "Always show message timestamps", "Autoplay GIFs and videos": "Autoplay GIFs and videos", - "Show a reminder to enable Secure Message Recovery in encrypted rooms": "Show a reminder to enable Secure Message Recovery in encrypted rooms", "Enable automatic language detection for syntax highlighting": "Enable automatic language detection for syntax highlighting", "Show avatars in user and room mentions": "Show avatars in user and room mentions", "Enable big emoji in chat": "Enable big emoji in chat", @@ -1172,12 +1171,6 @@ "%(roomName)s is not accessible at this time.": "%(roomName)s is not accessible at this time.", "Try again later, or ask a room admin to check if you have access.": "Try again later, or ask a room admin to check if you have access.", "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please submit a bug report.": "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please submit a bug report.", - "Start using Key Backup": "Start using Key Backup", - "Never lose encrypted messages": "Never lose encrypted messages", - "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.", - "Securely back up your keys to avoid losing them. Learn more.": "Securely back up your keys to avoid losing them. Learn more.", - "Not now": "Not now", - "Don't ask me again": "Don't ask me again", "Appearance": "Appearance", "Show rooms with unread messages first": "Show rooms with unread messages first", "Show previews of messages": "Show previews of messages", @@ -1750,6 +1743,7 @@ "%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!", "Updating %(brand)s": "Updating %(brand)s", "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.", + "Start using Key Backup": "Start using Key Backup", "I don't want my encrypted messages": "I don't want my encrypted messages", "Manually export keys": "Manually export keys", "You'll lose access to your encrypted messages": "You'll lose access to your encrypted messages", @@ -2300,9 +2294,6 @@ "Enter passphrase": "Enter passphrase", "Confirm passphrase": "Confirm passphrase", "Export": "Export", - "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.": "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.", - "If you don't want to set this up now, you can later in Settings.": "If you don't want to set this up now, you can later in Settings.", - "Don't ask again": "Don't ask again", "Import room keys": "Import room keys", "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.": "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.", "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.", diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index b35fa3db13..91bc4c2d85 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -281,11 +281,6 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: _td('Autoplay GIFs and videos'), default: false, }, - "showRoomRecoveryReminder": { - supportedLevels: LEVELS_ACCOUNT_SETTINGS, - displayName: _td('Show a reminder to enable Secure Message Recovery in encrypted rooms'), - default: true, - }, "enableSyntaxHighlightLanguageDetection": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, displayName: _td('Enable automatic language detection for syntax highlighting'), From 0d25f62a9adb9e4bb3bae6081f8c7eced3c82fc5 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 16 Sep 2020 12:00:49 +0100 Subject: [PATCH 251/286] Tweak diagnostics for session backup key --- src/components/views/settings/SecureBackupPanel.js | 2 +- src/rageshake/submit-rageshake.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js index 7e9fb6cd3d..3547efc3f2 100644 --- a/src/components/views/settings/SecureBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -131,7 +131,7 @@ export default class SecureBackupPanel extends React.PureComponent { const cli = MatrixClientPeg.get(); const secretStorage = cli._crypto._secretStorage; - const backupKeyStored = await cli.isKeyBackupKeyStored(); + const backupKeyStored = !!(await cli.isKeyBackupKeyStored()); const backupKeyFromCache = await cli._crypto.getSessionBackupPrivateKey(); const backupKeyCached = !!(backupKeyFromCache); const backupKeyWellFormed = backupKeyFromCache instanceof Uint8Array; diff --git a/src/rageshake/submit-rageshake.ts b/src/rageshake/submit-rageshake.ts index dd60cde16d..d361f6b0dd 100644 --- a/src/rageshake/submit-rageshake.ts +++ b/src/rageshake/submit-rageshake.ts @@ -112,6 +112,7 @@ async function collectBugReport(opts: IOpts = {}, gzipLogs = true) { body.append("secret_storage_ready", String(await client.isSecretStorageReady())); body.append("secret_storage_key_in_account", String(!!(await secretStorage.hasKey()))); + body.append("session_backup_key_in_secret_storage", String(!!(await client.isKeyBackupKeyStored()))); const sessionBackupKeyFromCache = await client._crypto.getSessionBackupPrivateKey(); body.append("session_backup_key_cached", String(!!sessionBackupKeyFromCache)); body.append("session_backup_key_well_formed", String(sessionBackupKeyFromCache instanceof Uint8Array)); From 6130d9e8826863b9faa0e763c55fc8dd49fb5ef5 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 16 Sep 2020 12:25:02 +0100 Subject: [PATCH 252/286] Delay encryption setup toasts until encrypted rooms present Part of https://github.com/vector-im/element-web/issues/13895 --- src/DeviceListener.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/DeviceListener.ts b/src/DeviceListener.ts index 89cf968c6b..df494e6bdd 100644 --- a/src/DeviceListener.ts +++ b/src/DeviceListener.ts @@ -29,7 +29,6 @@ import { hideToast as hideUnverifiedSessionsToast, showToast as showUnverifiedSessionsToast, } from "./toasts/UnverifiedSessionToast"; -import { privateShouldBeEncrypted } from "./createRoom"; import { isSecretStorageBeingAccessed, accessSecretStorage } from "./SecurityManager"; import { isSecureBackupRequired } from './utils/WellKnownUtils'; import { isLoggedIn } from './components/structures/MatrixChat'; @@ -201,9 +200,7 @@ export default class DeviceListener { // If we're in the middle of a secret storage operation, we're likely // modifying the state involved here, so don't add new toasts to setup. if (isSecretStorageBeingAccessed()) return false; - // In a default configuration, show the toasts. If the well-known config causes e2ee default to be false - // then do not show the toasts until user is in at least one encrypted room. - if (privateShouldBeEncrypted()) return true; + // Show setup toasts once the user is in at least one encrypted room. const cli = MatrixClientPeg.get(); return cli && cli.getRooms().some(r => cli.isRoomEncrypted(r.roomId)); } @@ -219,8 +216,6 @@ export default class DeviceListener { // (we add a listener on sync to do once check after the initial sync is done) if (!cli.isInitialSyncComplete()) return; - // JRS: This will change again in the next PR which moves secret storage - // later in the process. const crossSigningReady = await cli.isCrossSigningReady(); const secretStorageReady = await cli.isSecretStorageReady(); const allSystemsReady = crossSigningReady && secretStorageReady; From 550a53e49ce127db64767671e5b887d476861e40 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 16 Sep 2020 13:20:45 +0100 Subject: [PATCH 253/286] Check cross-signing cached keys when showing setup button --- src/components/views/settings/CrossSigningPanel.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index 669c2e84d9..1c548bd9d8 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -187,12 +187,18 @@ export default class CrossSigningPanel extends React.PureComponent { } const keysExistAnywhere = ( + crossSigningPublicKeysOnDevice || crossSigningPrivateKeysInStorage || - crossSigningPublicKeysOnDevice + masterPrivateKeyCached || + selfSigningPrivateKeyCached || + userSigningPrivateKeyCached ); const keysExistEverywhere = ( + crossSigningPublicKeysOnDevice && crossSigningPrivateKeysInStorage && - crossSigningPublicKeysOnDevice + masterPrivateKeyCached && + selfSigningPrivateKeyCached && + userSigningPrivateKeyCached ); const actions = []; From 7a5b0a964f041ad5f2e515f44166615ffef0ca4d Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 16 Sep 2020 13:57:23 +0100 Subject: [PATCH 254/286] Adjust main encryption toast to reference Secure Backup This adjusts the main toast to focus on Secure Backup as suggested in designs. Part of https://github.com/vector-im/element-web/issues/13895 --- res/css/structures/_ToastContainer.scss | 5 +++++ src/i18n/strings/en_EN.json | 7 +++---- src/toasts/SetupEncryptionToast.ts | 18 ++++++++++++++---- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/res/css/structures/_ToastContainer.scss b/res/css/structures/_ToastContainer.scss index 544dcbc180..c381668a6a 100644 --- a/res/css/structures/_ToastContainer.scss +++ b/res/css/structures/_ToastContainer.scss @@ -80,6 +80,11 @@ limitations under the License. } } + &.mx_Toast_icon_secure_backup::after { + mask-image: url('$(res)/img/feather-customised/secure-backup.svg'); + background-color: $primary-fg-color; + } + .mx_Toast_title, .mx_Toast_body { grid-column: 2; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 727898f1a0..17d80b7647 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -411,13 +411,12 @@ "Set password": "Set password", "To return to your account in future you need to set a password": "To return to your account in future you need to set a password", "Set Password": "Set Password", - "Set up encryption": "Set up encryption", + "Set up Secure Backup": "Set up Secure Backup", "Encryption upgrade available": "Encryption upgrade available", "Verify this session": "Verify this session", - "Set up": "Set up", "Upgrade": "Upgrade", "Verify": "Verify", - "Verify yourself & others to keep your chats safe": "Verify yourself & others to keep your chats safe", + "Safeguard against losing access to encrypted messages & data": "Safeguard against losing access to encrypted messages & data", "Other users may not trust it": "Other users may not trust it", "New login. Was this you?": "New login. Was this you?", "Verify the new login accessing your account: %(name)s": "Verify the new login accessing your account: %(name)s", @@ -651,6 +650,7 @@ "Cross-signing is ready for use.": "Cross-signing is ready for use.", "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.": "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.", "Cross-signing is not set up.": "Cross-signing is not set up.", + "Set up": "Set up", "Reset": "Reset", "Cross-signing public keys:": "Cross-signing public keys:", "in memory": "in memory", @@ -2279,7 +2279,6 @@ "Unable to query secret storage status": "Unable to query secret storage status", "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", "Set a Security Phrase": "Set a Security Phrase", "Confirm Security Phrase": "Confirm Security Phrase", diff --git a/src/toasts/SetupEncryptionToast.ts b/src/toasts/SetupEncryptionToast.ts index 5e3da94eda..5aa030e497 100644 --- a/src/toasts/SetupEncryptionToast.ts +++ b/src/toasts/SetupEncryptionToast.ts @@ -28,7 +28,7 @@ const TOAST_KEY = "setupencryption"; const getTitle = (kind: Kind) => { switch (kind) { case Kind.SET_UP_ENCRYPTION: - return _t("Set up encryption"); + return _t("Set up Secure Backup"); case Kind.UPGRADE_ENCRYPTION: return _t("Encryption upgrade available"); case Kind.VERIFY_THIS_SESSION: @@ -36,10 +36,20 @@ const getTitle = (kind: Kind) => { } }; +const getIcon = (kind: Kind) => { + switch (kind) { + case Kind.SET_UP_ENCRYPTION: + case Kind.UPGRADE_ENCRYPTION: + return "secure_backup"; + case Kind.VERIFY_THIS_SESSION: + return "verification_warning"; + } +}; + const getSetupCaption = (kind: Kind) => { switch (kind) { case Kind.SET_UP_ENCRYPTION: - return _t("Set up"); + return _t("Continue"); case Kind.UPGRADE_ENCRYPTION: return _t("Upgrade"); case Kind.VERIFY_THIS_SESSION: @@ -51,7 +61,7 @@ const getDescription = (kind: Kind) => { switch (kind) { case Kind.SET_UP_ENCRYPTION: case Kind.UPGRADE_ENCRYPTION: - return _t("Verify yourself & others to keep your chats safe"); + return _t("Safeguard against losing access to encrypted messages & data"); case Kind.VERIFY_THIS_SESSION: return _t("Other users may not trust it"); } @@ -88,7 +98,7 @@ export const showToast = (kind: Kind) => { ToastStore.sharedInstance().addOrReplaceToast({ key: TOAST_KEY, title: getTitle(kind), - icon: "verification_warning", + icon: getIcon(kind), props: { description: getDescription(kind), acceptLabel: getSetupCaption(kind), From feb37878d85f723039d737f8ffdf451706c9b9f4 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 15:04:13 +0100 Subject: [PATCH 255/286] tidy --- src/components/views/dialogs/InviteDialog.js | 35 ++++++++++---------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index f66de67a1d..73101056f3 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -1123,24 +1123,25 @@ export default class InviteDialog extends React.PureComponent { const inviteText = _t("This won't invite them to %(communityName)s. " + "To invite someone to %(communityName)s, click here", {communityName}, { - userId: () => { - return ( - {userId} - ); + userId: () => { + return ( + {userId} + ); + }, + a: (sub) => { + return ( + {sub} + ); + }, }, - a: (sub) => { - return ( - {sub} - ); - }, - }); + ); helpText = { helpText } {inviteText} ; From 06c4abd65e0da867319fb328eff22657616dd80a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 15:23:08 +0100 Subject: [PATCH 256/286] Clean up UserInfo to not show a blank Power Selector for users not in room --- src/components/views/right_panel/UserInfo.js | 51 +++++++++----------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index fc73c8b542..3171890955 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -952,30 +952,26 @@ function useRoomPermissions(cli, room, user) { const PowerLevelSection = ({user, room, roomPermissions, powerLevels}) => { const [isEditing, setEditing] = useState(false); - if (room && user.roomId) { // is in room - if (isEditing) { - return ( setEditing(false)} />); - } else { - const IconButton = sdk.getComponent('elements.IconButton'); - const powerLevelUsersDefault = powerLevels.users_default || 0; - const powerLevel = parseInt(user.powerLevel, 10); - const modifyButton = roomPermissions.canEdit ? - ( setEditing(true)} />) : null; - const role = textualPowerLevel(powerLevel, powerLevelUsersDefault); - const label = _t("%(role)s in %(roomName)s", - {role, roomName: room.name}, - {strong: label => {label}}, - ); - return ( -
    -
    {label}{modifyButton}
    -
    - ); - } + if (isEditing) { + return ( setEditing(false)} />); } else { - return null; + const IconButton = sdk.getComponent('elements.IconButton'); + const powerLevelUsersDefault = powerLevels.users_default || 0; + const powerLevel = parseInt(user.powerLevel, 10); + const modifyButton = roomPermissions.canEdit ? + ( setEditing(true)} />) : null; + const role = textualPowerLevel(powerLevel, powerLevelUsersDefault); + const label = _t("%(role)s in %(roomName)s", + {role, roomName: room.name}, + {strong: label => {label}}, + ); + return ( +
    +
    {label}{modifyButton}
    +
    + ); } }; @@ -1268,14 +1264,15 @@ const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => { spinner = ; } - const memberDetails = ( - - ); + />; + } // only display the devices list if our client supports E2E const cryptoEnabled = cli.isCryptoEnabled(); From c11abb74e0020610f1af696b463d64a44e9ddaf9 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 16:06:17 +0100 Subject: [PATCH 257/286] UI Feature Flag: Share dialog QR code and social icons --- res/css/views/dialogs/_ShareDialog.scss | 5 +- src/components/views/dialogs/ShareDialog.tsx | 53 ++++++++++++-------- src/settings/Settings.ts | 8 +++ src/settings/UIFeature.ts | 2 + 4 files changed, 46 insertions(+), 22 deletions(-) diff --git a/res/css/views/dialogs/_ShareDialog.scss b/res/css/views/dialogs/_ShareDialog.scss index c343b872fd..ce3fdd021f 100644 --- a/res/css/views/dialogs/_ShareDialog.scss +++ b/res/css/views/dialogs/_ShareDialog.scss @@ -71,9 +71,12 @@ limitations under the License. margin-right: 64px; } +.mx_ShareDialog_qrcode_container + .mx_ShareDialog_social_container { + width: 299px; +} + .mx_ShareDialog_social_container { display: inline-block; - width: 299px; } .mx_ShareDialog_social_icon { diff --git a/src/components/views/dialogs/ShareDialog.tsx b/src/components/views/dialogs/ShareDialog.tsx index e849f7efe3..1569977d58 100644 --- a/src/components/views/dialogs/ShareDialog.tsx +++ b/src/components/views/dialogs/ShareDialog.tsx @@ -32,6 +32,8 @@ import {copyPlaintext, selectText} from "../../../utils/strings"; import StyledCheckbox from '../elements/StyledCheckbox'; import AccessibleTooltipButton from '../elements/AccessibleTooltipButton'; import { IDialogProps } from "./IDialogProps"; +import SettingsStore from "../../../settings/SettingsStore"; +import {UIFeature} from "../../../settings/UIFeature"; const socials = [ { @@ -197,6 +199,35 @@ export default class ShareDialog extends React.PureComponent { const matrixToUrl = this.getUrl(); const encodedUrl = encodeURIComponent(matrixToUrl); + const showQrCode = SettingsStore.getValue(UIFeature.ShareQRCode); + const showSocials = SettingsStore.getValue(UIFeature.ShareSocial); + + let qrSocialSection; + if (showQrCode || showSocials) { + qrSocialSection = <> +
    +
    + { showQrCode &&
    + +
    } + { showSocials &&
    + { socials.map((social) => ( + + {social.name} + + )) } +
    } +
    + ; + } + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); return { />
    { checkbox } -
    - -
    -
    - -
    -
    - { socials.map((social) => ( - - {social.name} - - )) } -
    -
    + { qrSocialSection }
    ; } diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index b35fa3db13..3731125f09 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -622,4 +622,12 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_UI_FEATURE, default: true, }, + [UIFeature.ShareQRCode]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, + [UIFeature.ShareSocial]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, }; diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index 99196e5d30..c4825dbbba 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -18,4 +18,6 @@ limitations under the License. export enum UIFeature { URLPreviews = "UIFeature.urlPreviews", Widgets = "UIFeature.widgets", + ShareQRCode = "UIFeature.shareQrCode", + ShareSocial = "UIFeature.shareSocial", } From 93323febcba986cee7fbead49de882fd17b4c72b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 16:10:24 +0100 Subject: [PATCH 258/286] undo change --- src/components/views/settings/SecureBackupPanel.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js index c058e27de7..7f0655d54a 100644 --- a/src/components/views/settings/SecureBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -367,14 +367,14 @@ export default class SecureBackupPanel extends React.PureComponent { ; actions.push( - + {restoreButtonCaption} , ); if (!isSecureBackupRequired()) { actions.push( - + {_t("Delete Backup")} , ); @@ -388,7 +388,7 @@ export default class SecureBackupPanel extends React.PureComponent {

    {_t("Back up your keys before signing out to avoid losing them.")}

    ; actions.push( - + {_t("Set up")} , ); @@ -396,7 +396,7 @@ export default class SecureBackupPanel extends React.PureComponent { if (secretStorageKeyInAccount) { actions.push( - + {_t("Reset")} , ); From 9be04f8a67790a6a849c213aa1a9b696f1a0fa59 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Sep 2020 16:10:43 +0100 Subject: [PATCH 259/286] 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 070cf7e597..e8803ddc32 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -912,13 +912,13 @@ "Message search": "Message search", "Cross-signing": "Cross-signing", "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.", + "Privacy": "Privacy", "%(brand)s collects anonymous analytics to allow us to improve the application.": "%(brand)s collects anonymous analytics to allow us to improve the application.", "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.": "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.", "Learn more about how we use analytics.": "Learn more about how we use analytics.", "Where you’re logged in": "Where you’re logged in", "Manage the names of and sign out of your sessions below or verify them in your User Profile.": "Manage the names of and sign out of your sessions below or verify them in your User Profile.", "A session's public name is visible to people you communicate with": "A session's public name is visible to people you communicate with", - "Privacy": "Privacy", "No media permissions": "No media permissions", "You may need to manually permit %(brand)s to access your microphone/webcam": "You may need to manually permit %(brand)s to access your microphone/webcam", "Missing media permissions, click the button below to request.": "Missing media permissions, click the button below to request.", From a18d0271c370c25706d9174c74bc7089be05d18d Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 16 Sep 2020 16:51:10 +0100 Subject: [PATCH 260/286] Adjust tests for Secure Backup toast --- .../src/scenarios/e2e-encryption.js | 2 + .../end-to-end-tests/src/usecases/security.js | 42 +++++++++++++++++++ test/end-to-end-tests/src/usecases/signup.js | 2 + 3 files changed, 46 insertions(+) create mode 100644 test/end-to-end-tests/src/usecases/security.js diff --git a/test/end-to-end-tests/src/scenarios/e2e-encryption.js b/test/end-to-end-tests/src/scenarios/e2e-encryption.js index d31d2c0d57..20e8af2947 100644 --- a/test/end-to-end-tests/src/scenarios/e2e-encryption.js +++ b/test/end-to-end-tests/src/scenarios/e2e-encryption.js @@ -21,6 +21,7 @@ const {receiveMessage} = require('../usecases/timeline'); const {createDm} = require('../usecases/create-room'); const {checkRoomSettings} = require('../usecases/room-settings'); const {startSasVerifcation, acceptSasVerification} = require('../usecases/verify'); +const { setupSecureBackup } = require('../usecases/security'); const assert = require('assert'); module.exports = async function e2eEncryptionScenarios(alice, bob) { @@ -43,4 +44,5 @@ module.exports = async function e2eEncryptionScenarios(alice, bob) { const bobMessage = "You've got to tell me!"; await sendMessage(bob, bobMessage); await receiveMessage(alice, {sender: "bob", body: bobMessage, encrypted: true}); + await setupSecureBackup(alice); }; diff --git a/test/end-to-end-tests/src/usecases/security.js b/test/end-to-end-tests/src/usecases/security.js new file mode 100644 index 0000000000..31540874e9 --- /dev/null +++ b/test/end-to-end-tests/src/usecases/security.js @@ -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. +*/ + +const { acceptToast } = require("./toasts"); + +async function setupSecureBackup(session) { + session.log.step("sets up Secure Backup"); + + await acceptToast(session, "Set up Secure Backup"); + + // Continue with the default (generate a security key) + const 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_buttons .mx_Dialog_primary', + ); + await copyContinueButton.click(); + + session.log.done(); +} + +module.exports = { setupSecureBackup }; diff --git a/test/end-to-end-tests/src/usecases/signup.js b/test/end-to-end-tests/src/usecases/signup.js index ef8a259091..e4b2a9a62d 100644 --- a/test/end-to-end-tests/src/usecases/signup.js +++ b/test/end-to-end-tests/src/usecases/signup.js @@ -15,6 +15,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +const { acceptToast } = require("./toasts"); + const assert = require('assert'); module.exports = async function signup(session, username, password, homeserver) { From b5000b236f09dd4159f1768121d62a801aabe7b1 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 16 Sep 2020 17:12:51 +0100 Subject: [PATCH 261/286] Fix lint error --- test/end-to-end-tests/src/usecases/signup.js | 2 -- 1 file changed, 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 e4b2a9a62d..ef8a259091 100644 --- a/test/end-to-end-tests/src/usecases/signup.js +++ b/test/end-to-end-tests/src/usecases/signup.js @@ -15,8 +15,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -const { acceptToast } = require("./toasts"); - const assert = require('assert'); module.exports = async function signup(session, username, password, homeserver) { From 815a1559ffa8af51ab9794771a5915549e3cb9e2 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 16 Sep 2020 15:05:14 -0600 Subject: [PATCH 262/286] Fix setState() usage in the constructor of RoomDirectory React doesn't like it when we setState() in the constructor --- src/components/structures/RoomDirectory.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js index 16ab8edbed..44652e5073 100644 --- a/src/components/structures/RoomDirectory.js +++ b/src/components/structures/RoomDirectory.js @@ -70,10 +70,10 @@ export default class RoomDirectory extends React.Component { this.scrollPanel = null; this.protocols = null; - this.setState({protocolsLoading: true}); + this.state.protocolsLoading = true; if (!MatrixClientPeg.get()) { // We may not have a client yet when invoked from welcome page - this.setState({protocolsLoading: false}); + this.state.protocolsLoading = false; return; } @@ -102,14 +102,16 @@ export default class RoomDirectory extends React.Component { }); } else { // We don't use the protocols in the communities v2 prototype experience - this.setState({protocolsLoading: false}); + this.state.protocolsLoading = false; // Grab the profile info async FlairStore.getGroupProfileCached(MatrixClientPeg.get(), this.state.selectedCommunityId).then(profile => { this.setState({communityName: profile.name}); }); } + } + componentDidMount() { this.refreshRoomList(); } From d340dd58d1f4828f45dbf243d436bb21f32e1dca Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 17 Sep 2020 11:55:10 +0100 Subject: [PATCH 263/286] UI Feature Flag: Registration, Password Reset, Deactivate --- res/css/views/auth/_Welcome.scss | 6 ++++++ src/components/structures/MatrixChat.tsx | 10 ++++++---- src/components/structures/auth/Login.js | 4 +++- src/components/views/auth/Welcome.js | 8 +++++++- .../settings/tabs/user/GeneralUserSettingsTab.js | 11 +++++++++-- src/settings/Settings.ts | 12 ++++++++++++ src/settings/UIFeature.ts | 3 +++ 7 files changed, 46 insertions(+), 8 deletions(-) diff --git a/res/css/views/auth/_Welcome.scss b/res/css/views/auth/_Welcome.scss index 9043289184..f0e2b3de33 100644 --- a/res/css/views/auth/_Welcome.scss +++ b/res/css/views/auth/_Welcome.scss @@ -18,6 +18,12 @@ limitations under the License. display: flex; flex-direction: column; align-items: center; + + &.mx_WelcomePage_registrationDisabled { + .mx_ButtonCreateAccount { + display: none; + } + } } .mx_Welcome .mx_AuthBody_language { diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 1875d80fa4..26d1941574 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -79,6 +79,7 @@ import { SettingLevel } from "../../settings/SettingLevel"; import { leaveRoomBehaviour } from "../../utils/membership"; import CreateCommunityPrototypeDialog from "../views/dialogs/CreateCommunityPrototypeDialog"; import ThreepidInviteStore, { IThreepidInvite, IThreepidInviteWireFormat } from "../../stores/ThreepidInviteStore"; +import {UIFeature} from "../../settings/UIFeature"; /** constants for MatrixChat.state.view */ export enum Views { @@ -1942,7 +1943,7 @@ export default class MatrixChat extends React.PureComponent { render() { const fragmentAfterLogin = this.getFragmentAfterLogin(); - let view; + let view = null; if (this.state.view === Views.LOADING) { const Spinner = sdk.getComponent('elements.Spinner'); @@ -2021,7 +2022,7 @@ export default class MatrixChat extends React.PureComponent { } else if (this.state.view === Views.WELCOME) { const Welcome = sdk.getComponent('auth.Welcome'); view = ; - } else if (this.state.view === Views.REGISTER) { + } else if (this.state.view === Views.REGISTER && SettingsStore.getValue(UIFeature.Registration)) { const Registration = sdk.getComponent('structures.auth.Registration'); const email = ThreepidInviteStore.instance.pickBestInvite()?.toEmail; view = ( @@ -2039,7 +2040,7 @@ export default class MatrixChat extends React.PureComponent { {...this.getServerProperties()} /> ); - } else if (this.state.view === Views.FORGOT_PASSWORD) { + } else if (this.state.view === Views.FORGOT_PASSWORD && SettingsStore.getValue(UIFeature.PasswordReset)) { const ForgotPassword = sdk.getComponent('structures.auth.ForgotPassword'); view = ( { /> ); } else if (this.state.view === Views.LOGIN) { + const showPasswordReset = SettingsStore.getValue(UIFeature.PasswordReset); const Login = sdk.getComponent('structures.auth.Login'); view = ( { onRegisterClick={this.onRegisterClick} fallbackHsUrl={this.getFallbackHsUrl()} defaultDeviceDisplayName={this.props.defaultDeviceDisplayName} - onForgotPasswordClick={this.onForgotPasswordClick} + onForgotPasswordClick={showPasswordReset ? this.onForgotPasswordClick : undefined} onServerConfigChange={this.onServerConfigChange} fragmentAfterLogin={fragmentAfterLogin} {...this.getServerProperties()} diff --git a/src/components/structures/auth/Login.js b/src/components/structures/auth/Login.js index a20bf0dd0a..118eed59e3 100644 --- a/src/components/structures/auth/Login.js +++ b/src/components/structures/auth/Login.js @@ -28,6 +28,8 @@ import classNames from "classnames"; import AuthPage from "../../views/auth/AuthPage"; import SSOButton from "../../views/elements/SSOButton"; import PlatformPeg from '../../../PlatformPeg'; +import SettingsStore from "../../../settings/SettingsStore"; +import {UIFeature} from "../../../settings/UIFeature"; // For validating phone numbers without country codes const PHONE_NUMBER_REGEX = /^[0-9()\-\s]*$/; @@ -679,7 +681,7 @@ export default class LoginComponent extends React.Component { {_t("If you've joined lots of rooms, this might take a while")}
    }
    ; - } else { + } else if (SettingsStore.getValue(UIFeature.Registration)) { footer = ( { _t('Create account') } diff --git a/src/components/views/auth/Welcome.js b/src/components/views/auth/Welcome.js index 5a30a02490..21032f4f1a 100644 --- a/src/components/views/auth/Welcome.js +++ b/src/components/views/auth/Welcome.js @@ -15,10 +15,14 @@ limitations under the License. */ import React from 'react'; +import classNames from "classnames"; + import * as sdk from '../../../index'; import SdkConfig from '../../../SdkConfig'; import AuthPage from "./AuthPage"; import {_td} from "../../../languageHandler"; +import SettingsStore from "../../../settings/SettingsStore"; +import {UIFeature} from "../../../settings/UIFeature"; // translatable strings for Welcome pages _td("Sign in with SSO"); @@ -39,7 +43,9 @@ export default class Welcome extends React.PureComponent { return ( -
    +
    : null; + let accountManagementSection; + if (SettingsStore.getValue(UIFeature.Deactivate)) { + accountManagementSection = <> +
    {_t("Deactivate account")}
    + {this._renderManagementSection()} + ; + } + return (
    {_t("General")}
    @@ -395,8 +403,7 @@ export default class GeneralUserSettingsTab extends React.Component {
    {discoWarning} {_t("Discovery")}
    {this._renderDiscoverySection()} {this._renderIntegrationManagerSection() /* Has its own title */} -
    {_t("Deactivate account")}
    - {this._renderManagementSection()} + { accountManagementSection }
    ); } diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 21b3935c3e..f7a1b6655c 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -626,4 +626,16 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_UI_FEATURE, default: true, }, + [UIFeature.Registration]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, + [UIFeature.PasswordReset]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, + [UIFeature.Deactivate]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, }; diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index dddef82df1..71821917bf 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -19,4 +19,7 @@ export enum UIFeature { URLPreviews = "UIFeature.urlPreviews", Widgets = "UIFeature.widgets", Feedback = "UIFeature.feedback", + Registration = "UIFeature.registration", + PasswordReset = "UIFeature.passwordReset", + Deactivate = "UIFeature.deactivate", } From f52b267bd39a19872a0ff3300925f0ce9e403123 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 17 Sep 2020 12:07:17 +0100 Subject: [PATCH 264/286] 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 b2b4e01202..d91fe475df 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -832,9 +832,9 @@ "Account management": "Account management", "Deactivating your account is a permanent action - be careful!": "Deactivating your account is a permanent action - be careful!", "Deactivate Account": "Deactivate Account", + "Deactivate account": "Deactivate account", "General": "General", "Discovery": "Discovery", - "Deactivate account": "Deactivate account", "Legal": "Legal", "Credits": "Credits", "For help with using %(brand)s, click
    here.": "For help with using %(brand)s, click here.", From eda2dee63fd3be2d1f3b8d5a5bede8d46c4bfeb2 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 17 Sep 2020 13:25:18 +0100 Subject: [PATCH 265/286] UI Feature Flag: 3PIDs --- .../views/settings/tabs/user/GeneralUserSettingsTab.js | 4 +++- src/settings/Settings.ts | 4 ++++ src/settings/UIFeature.ts | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index 42e12077f2..fadb4c756b 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -248,7 +248,9 @@ export default class GeneralUserSettingsTab extends React.Component { // validate 3PID ownership even if we're just adding to the homeserver only. // For newer homeservers with separate 3PID add and bind methods (MSC2290), // there is no such concern, so we can always show the HS account 3PIDs. - if (this.state.haveIdServer || this.state.serverSupportsSeparateAddAndBind === true) { + if (SettingsStore.getValue(UIFeature.ThirdPartyID) && + (this.state.haveIdServer || this.state.serverSupportsSeparateAddAndBind === true) + ) { const emails = this.state.loading3pids ? : Date: Thu, 17 Sep 2020 13:57:47 +0100 Subject: [PATCH 266/286] i18n --- src/i18n/strings/en_EN.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d91fe475df..4c2a55d09e 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -833,8 +833,8 @@ "Deactivating your account is a permanent action - be careful!": "Deactivating your account is a permanent action - be careful!", "Deactivate Account": "Deactivate Account", "Deactivate account": "Deactivate account", - "General": "General", "Discovery": "Discovery", + "General": "General", "Legal": "Legal", "Credits": "Credits", "For help with using %(brand)s, click here.": "For help with using %(brand)s, click here.", @@ -1732,9 +1732,11 @@ "Recently Direct Messaged": "Recently Direct Messaged", "Direct Messages": "Direct Messages", "Start a conversation with someone using their name, username (like ) or email address.": "Start a conversation with someone using their name, username (like ) or email address.", - "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.", + "Start a conversation with someone using their name or username (like ).": "Start a conversation with someone using their name or username (like ).", + "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here": "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here", "Go": "Go", "Invite someone using their name, username (like ), email address or share this room.": "Invite someone using their name, username (like ), email address or share this room.", + "Invite someone using their name, username (like ) or share this room.": "Invite someone using their name, username (like ) or share this room.", "a new master key signature": "a new master key signature", "a new cross-signing key signature": "a new cross-signing key signature", "a device cross-signing signature": "a device cross-signing signature", From 24d0950b7edd73dc16ba8a9860df20775645f0f5 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 17 Sep 2020 09:23:06 -0600 Subject: [PATCH 267/286] Adjust layout and formatting of notifications / file cards This follows some polish time with a designer. The placeholder text on the two panels was tracking up/down when the width was changed. This is fixed by adjusting some of the flexbox properties to center it more safely. We also spent some time making the notifications panel more legible while we wait for the overhaul to land. --- res/css/structures/_FilePanel.scss | 7 ++++ res/css/structures/_NotificationPanel.scss | 37 ++++++++++++++++++---- res/css/structures/_RoomView.scss | 8 ++--- src/components/views/rooms/EventTile.js | 2 ++ 4 files changed, 43 insertions(+), 11 deletions(-) diff --git a/res/css/structures/_FilePanel.scss b/res/css/structures/_FilePanel.scss index 21b30d804a..2aa068b674 100644 --- a/res/css/structures/_FilePanel.scss +++ b/res/css/structures/_FilePanel.scss @@ -23,6 +23,13 @@ limitations under the License. .mx_FilePanel .mx_RoomView_messageListWrapper { margin-right: 20px; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.mx_FilePanel .mx_RoomView_MessageList { + width: 100%; } .mx_FilePanel .mx_RoomView_MessageList h2 { diff --git a/res/css/structures/_NotificationPanel.scss b/res/css/structures/_NotificationPanel.scss index 715a94fe2c..2da334b385 100644 --- a/res/css/structures/_NotificationPanel.scss +++ b/res/css/structures/_NotificationPanel.scss @@ -22,7 +22,13 @@ limitations under the License. } .mx_NotificationPanel .mx_RoomView_messageListWrapper { - margin-right: 20px; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.mx_NotificationPanel .mx_RoomView_MessageList { + width: 100%; } .mx_NotificationPanel .mx_RoomView_MessageList h2 { @@ -35,11 +41,32 @@ limitations under the License. .mx_NotificationPanel .mx_EventTile { word-break: break-word; + position: relative; + padding-bottom: 18px; + + &:not(.mx_EventTile_last)::after { + position: absolute; + bottom: 0; + left: 0; + right: 0; + background-color: $tertiary-fg-color; + height: 1px; + opacity: 0.4; + content: ''; + } } .mx_NotificationPanel .mx_EventTile_roomName { font-weight: bold; font-size: $font-14px; + + > * { + vertical-align: middle; + } + + > .mx_BaseAvatar { + margin-right: 8px; + } } .mx_NotificationPanel .mx_EventTile_roomName a { @@ -47,8 +74,7 @@ limitations under the License. } .mx_NotificationPanel .mx_EventTile_avatar { - top: 8px; - left: 0px; + display: none; } .mx_NotificationPanel .mx_EventTile .mx_SenderProfile, @@ -60,8 +86,7 @@ limitations under the License. } .mx_NotificationPanel .mx_EventTile_senderDetails { - padding-left: 32px; - padding-top: 8px; + padding-left: 36px; position: relative; a { @@ -82,7 +107,7 @@ limitations under the License. .mx_NotificationPanel .mx_EventTile_line { margin-right: 0px; - padding-left: 32px; + padding-left: 36px; padding-top: 0px; padding-bottom: 0px; padding-right: 0px; diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 3b60c4e62b..f63f80f470 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -185,13 +185,11 @@ limitations under the License. } .mx_RoomView_empty { - flex: 1 1 auto; font-size: $font-13px; - padding-left: 3em; - padding-right: 3em; - margin-right: 20px; - margin-top: 33%; + padding: 0 24px; + margin-right: 30px; text-align: center; + margin-bottom: 80px; } .mx_RoomView_MessageList { diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index f444fb1f1a..121930b294 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -34,6 +34,7 @@ import * as ObjectUtils from "../../../ObjectUtils"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import {E2E_STATE} from "./E2EIcon"; import {toRem} from "../../../utils/units"; +import RoomAvatar from "../avatars/RoomAvatar"; const eventTileTypes = { 'm.room.message': 'messages.MessageEvent', @@ -821,6 +822,7 @@ export default class EventTile extends React.Component { return (
    + { room ? room.name : '' } From 14a7b839880761a63aac457664b0aad637cc79a8 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 17 Sep 2020 10:40:48 -0600 Subject: [PATCH 268/286] Don't show a bottom border ahead of the date separator --- res/css/structures/_NotificationPanel.scss | 2 +- src/components/structures/MessagePanel.js | 13 +++++++++++-- src/components/views/rooms/EventTile.js | 5 +++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/res/css/structures/_NotificationPanel.scss b/res/css/structures/_NotificationPanel.scss index 2da334b385..6366bcaec5 100644 --- a/res/css/structures/_NotificationPanel.scss +++ b/res/css/structures/_NotificationPanel.scss @@ -44,7 +44,7 @@ limitations under the License. position: relative; padding-bottom: 18px; - &:not(.mx_EventTile_last)::after { + &:not(.mx_EventTile_last):not(.mx_EventTile_lastInSection)::after { position: absolute; bottom: 0; left: 0; diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index fe7b20a2d9..e2e3592536 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -518,10 +518,13 @@ export default class MessagePanel extends React.Component { if (!grouper) { const wantTile = this._shouldShowEvent(mxEv); if (wantTile) { + const nextEvent = i < this.props.events.length - 1 + ? this.props.events[i + 1] + : null; // make sure we unpack the array returned by _getTilesForEvent, // otherwise react will auto-generate keys and we will end up // replacing all of the DOM elements every time we paginate. - ret.push(...this._getTilesForEvent(prevEvent, mxEv, last)); + ret.push(...this._getTilesForEvent(prevEvent, mxEv, last, nextEvent)); prevEvent = mxEv; } @@ -537,7 +540,7 @@ export default class MessagePanel extends React.Component { return ret; } - _getTilesForEvent(prevEvent, mxEv, last) { + _getTilesForEvent(prevEvent, mxEv, last, nextEvent) { const TileErrorBoundary = sdk.getComponent('messages.TileErrorBoundary'); const EventTile = sdk.getComponent('rooms.EventTile'); const DateSeparator = sdk.getComponent('messages.DateSeparator'); @@ -562,6 +565,11 @@ export default class MessagePanel extends React.Component { ret.push(dateSeparator); } + let willWantDateSeparator = false; + if (nextEvent) { + willWantDateSeparator = this._wantsDateSeparator(mxEv, nextEvent.getDate() || new Date()); + } + // is this a continuation of the previous message? const continuation = !wantsDateSeparator && shouldFormContinuation(prevEvent, mxEv); @@ -598,6 +606,7 @@ export default class MessagePanel extends React.Component { isTwelveHour={this.props.isTwelveHour} permalinkCreator={this.props.permalinkCreator} last={last} + lastInSection={willWantDateSeparator} isSelectedEvent={highlight} getRelationsForEvent={this.props.getRelationsForEvent} showReactions={this.props.showReactions} diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 121930b294..a1cc681a4c 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -148,6 +148,10 @@ export default class EventTile extends React.Component { */ last: PropTypes.bool, + // true if the event is the last event in a section (adds a css class for + // targeting) + lastInSection: PropTypes.bool, + /* true if this is search context (which has the effect of greying out * the text */ @@ -674,6 +678,7 @@ export default class EventTile extends React.Component { mx_EventTile_selected: this.props.isSelectedEvent, mx_EventTile_continuation: this.props.tileShape ? '' : this.props.continuation, mx_EventTile_last: this.props.last, + mx_EventTile_lastInSection: this.props.lastInSection, mx_EventTile_contextual: this.props.contextual, mx_EventTile_actionBarFocused: this.state.actionBarFocused, mx_EventTile_verified: !isBubbleMessage && this.state.verified === E2E_STATE.VERIFIED, From 4657a34bbb9715edca6b53e0f398c2443fae7eff Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 17 Sep 2020 10:44:18 -0600 Subject: [PATCH 269/286] Document some of the magic values --- res/css/structures/_NotificationPanel.scss | 6 +++--- res/css/structures/_RoomView.scss | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/res/css/structures/_NotificationPanel.scss b/res/css/structures/_NotificationPanel.scss index 6366bcaec5..8282b92bc4 100644 --- a/res/css/structures/_NotificationPanel.scss +++ b/res/css/structures/_NotificationPanel.scss @@ -74,7 +74,7 @@ limitations under the License. } .mx_NotificationPanel .mx_EventTile_avatar { - display: none; + display: none; // we don't need this in this view } .mx_NotificationPanel .mx_EventTile .mx_SenderProfile, @@ -86,7 +86,7 @@ limitations under the License. } .mx_NotificationPanel .mx_EventTile_senderDetails { - padding-left: 36px; + padding-left: 36px; // align with the room name position: relative; a { @@ -107,7 +107,7 @@ limitations under the License. .mx_NotificationPanel .mx_EventTile_line { margin-right: 0px; - padding-left: 36px; + padding-left: 36px; // align with the room name padding-top: 0px; padding-bottom: 0px; padding-right: 0px; diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index f63f80f470..572c7166d2 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -189,7 +189,7 @@ limitations under the License. padding: 0 24px; margin-right: 30px; text-align: center; - margin-bottom: 80px; + margin-bottom: 80px; // visually center the content (intentional offset) } .mx_RoomView_MessageList { From f5f48cbc21bef4526ef967772e7307762702b861 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 17 Sep 2020 10:44:58 -0600 Subject: [PATCH 270/286] Fix indentation --- res/css/structures/_NotificationPanel.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/structures/_NotificationPanel.scss b/res/css/structures/_NotificationPanel.scss index 8282b92bc4..1258ace069 100644 --- a/res/css/structures/_NotificationPanel.scss +++ b/res/css/structures/_NotificationPanel.scss @@ -65,7 +65,7 @@ limitations under the License. } > .mx_BaseAvatar { - margin-right: 8px; + margin-right: 8px; } } From 38f8c0a8358e403326a111c6fce9bdaba3dff593 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 17 Sep 2020 22:46:01 -0600 Subject: [PATCH 271/286] Disable the e2ee toggle when creating a room on a server with forced e2e Fixes https://github.com/vector-im/element-web/issues/15186 Requires https://github.com/matrix-org/matrix-js-sdk/pull/1470 --- .../views/dialogs/CreateRoomDialog.js | 19 +++++++++++++++++-- src/i18n/strings/en_EN.json | 1 + 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/components/views/dialogs/CreateRoomDialog.js b/src/components/views/dialogs/CreateRoomDialog.js index 21d48409e8..2b6bb5e187 100644 --- a/src/components/views/dialogs/CreateRoomDialog.js +++ b/src/components/views/dialogs/CreateRoomDialog.js @@ -45,7 +45,11 @@ export default class CreateRoomDialog extends React.Component { detailsOpen: false, noFederate: config.default_federate === false, nameIsValid: false, + canChangeEncryption: true, }; + + MatrixClientPeg.get().doesServerForceEncryptionForPreset("private") + .then(isForced => this.setState({canChangeEncryption: !isForced})); } _roomCreateOptions() { @@ -68,7 +72,13 @@ export default class CreateRoomDialog extends React.Component { } if (!this.state.isPublic) { - opts.encryption = this.state.isEncrypted; + if (this.state.canChangeEncryption) { + opts.encryption = this.state.isEncrypted; + } else { + // the server should automatically do this for us, but for safety + // we'll demand it too. + opts.encryption = true; + } } if (CommunityPrototypeStore.instance.getSelectedCommunityId()) { @@ -208,7 +218,11 @@ export default class CreateRoomDialog extends React.Component { if (!this.state.isPublic) { let microcopy; if (privateShouldBeEncrypted()) { - microcopy = _t("You can’t disable this later. Bridges & most bots won’t work yet."); + if (this.state.canChangeEncryption) { + microcopy = _t("You can’t disable this later. Bridges & most bots won’t work yet."); + } else { + microcopy = _t("Your server requires encryption to be enabled in private rooms."); + } } else { microcopy = _t("Your server admin has disabled end-to-end encryption by default " + "in private rooms & Direct Messages."); @@ -219,6 +233,7 @@ export default class CreateRoomDialog extends React.Component { onChange={this.onEncryptedChange} value={this.state.isEncrypted} className='mx_CreateRoomDialog_e2eSwitch' // for end-to-end tests + disabled={!this.state.canChangeEncryption} />

    { microcopy }

    ; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4c2a55d09e..177d02a3e6 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1659,6 +1659,7 @@ "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.", "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.", "You can’t disable this later. Bridges & most bots won’t work yet.": "You can’t disable this later. Bridges & most bots won’t work yet.", + "Your server requires encryption to be enabled in private rooms.": "Your server requires encryption to be enabled in private rooms.", "Enable end-to-end encryption": "Enable end-to-end encryption", "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.", "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.", From 1a965b1cb704f50ec2ea7ab9494451b3d828120e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 18 Sep 2020 11:15:48 +0100 Subject: [PATCH 272/286] UIF 3PID implies UIF Identity Server Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/settings/Settings.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 4a4594b6dc..2a699897a6 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -649,6 +649,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { [UIFeature.IdentityServer]: { supportedLevels: LEVELS_UI_FEATURE, default: true, + // Identity Server (Discovery) Settings make no sense if 3PIDs in general are hidden + controller: new UIFeatureController(UIFeature.ThirdPartyID), }, [UIFeature.ThirdPartyID]: { supportedLevels: LEVELS_UI_FEATURE, From 9dbc1dbc85cb45e7dc92473dfaa6e0ae95fc1b06 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 18 Sep 2020 11:34:35 +0100 Subject: [PATCH 273/286] Hide Advanced Appearance Settings Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../views/settings/tabs/user/AppearanceUserSettingsTab.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx index b4c05a2ecb..9f9acd8e3c 100644 --- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx @@ -36,6 +36,7 @@ import EventTilePreview from '../../../elements/EventTilePreview'; import StyledRadioGroup from "../../../elements/StyledRadioGroup"; import classNames from 'classnames'; import { SettingLevel } from "../../../../../settings/SettingLevel"; +import {UIFeature} from "../../../../../settings/UIFeature"; interface IProps { } @@ -386,6 +387,8 @@ export default class AppearanceUserSettingsTab extends React.Component Date: Fri, 18 Sep 2020 12:15:56 +0100 Subject: [PATCH 274/286] Fix Search Results Tile undefined variable access regression --- src/components/views/rooms/SearchResultTile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/SearchResultTile.js b/src/components/views/rooms/SearchResultTile.js index 8b2a9c2d61..29def9e368 100644 --- a/src/components/views/rooms/SearchResultTile.js +++ b/src/components/views/rooms/SearchResultTile.js @@ -71,7 +71,7 @@ export default class SearchResultTile extends React.Component { } } return ( -
  • +
  • { ret }
  • ); } From 780aea1a3653a6c56b392efe3832f1233413c0d8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 18 Sep 2020 12:32:33 +0100 Subject: [PATCH 275/286] Mac sends lowercase event.key even when holding Shift unlike Windows --- src/components/structures/RoomView.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index f568f31dbd..59a5efda91 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -672,6 +672,7 @@ export default class RoomView extends React.Component { handled = true; } break; + case Key.U: // Mac returns lowercase case Key.U.toUpperCase(): if (isOnlyCtrlOrCmdIgnoreShiftKeyEvent(ev) && ev.shiftKey) { dis.dispatch({ action: "upload_file" }); From d267092b192d85934d424a30897c0ed2663ac06e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 18 Sep 2020 12:33:25 +0100 Subject: [PATCH 276/286] Make the upload_file dispatch synchronous to make Firefox happy about it --- src/components/structures/RoomView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 59a5efda91..4c418e9994 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -675,7 +675,7 @@ export default class RoomView extends React.Component { case Key.U: // Mac returns lowercase case Key.U.toUpperCase(): if (isOnlyCtrlOrCmdIgnoreShiftKeyEvent(ev) && ev.shiftKey) { - dis.dispatch({ action: "upload_file" }); + dis.dispatch({ action: "upload_file" }, true); handled = true; } break; From adcb75facbefec580207bc976cd652e908e73348 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 18 Sep 2020 14:34:51 +0100 Subject: [PATCH 277/286] Only show User Info verify button if the other user has e2ee devices Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/right_panel/UserInfo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index 3171890955..a02b53b413 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -1306,7 +1306,7 @@ const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => { const showDeviceListSpinner = devices === undefined; if (canVerify) { - if (hasCrossSigningKeys !== undefined) { + if (hasCrossSigningKeys !== undefined && devices.length > 0) { // Note: mx_UserInfo_verifyButton is for the end-to-end tests verifyButton = ( { From 004c68b394e04d9e39a106079cee08bded7290fc Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 18 Sep 2020 15:43:08 +0100 Subject: [PATCH 278/286] Fix Room Directory View & Preview actions for federated joins Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/RoomDirectory.js | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js index 44652e5073..55c6527f06 100644 --- a/src/components/structures/RoomDirectory.js +++ b/src/components/structures/RoomDirectory.js @@ -392,22 +392,12 @@ export default class RoomDirectory extends React.Component { }; onPreviewClick = (ev, room) => { - this.props.onFinished(); - dis.dispatch({ - action: 'view_room', - room_id: room.room_id, - should_peek: true, - }); + this.showRoom(room, null, false, true); ev.stopPropagation(); }; onViewClick = (ev, room) => { - this.props.onFinished(); - dis.dispatch({ - action: 'view_room', - room_id: room.room_id, - should_peek: false, - }); + this.showRoom(room); ev.stopPropagation(); }; @@ -428,11 +418,12 @@ export default class RoomDirectory extends React.Component { this.showRoom(null, alias, autoJoin); } - showRoom(room, room_alias, autoJoin=false) { + showRoom(room, room_alias, autoJoin = false, shouldPeek = false) { this.props.onFinished(); const payload = { action: 'view_room', auto_join: autoJoin, + should_peek: shouldPeek, }; if (room) { // Don't let the user view a room they won't be able to either @@ -457,6 +448,7 @@ export default class RoomDirectory extends React.Component { }; if (this.state.roomServer) { + payload.via_servers = [this.state.roomServer]; payload.opts = { viaServers: [this.state.roomServer], }; From 949b8d9afe39e60ab264d71f4e3ef2959996fefc Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 18 Sep 2020 16:22:35 +0100 Subject: [PATCH 279/286] Rename apps back to widgets Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/elements/AppTile.js | 4 ++-- src/components/views/right_panel/RoomSummaryCard.tsx | 4 ++-- src/components/views/right_panel/WidgetCard.tsx | 2 +- src/i18n/strings/en_EN.json | 12 ++++++------ 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 79a88ed43d..6aaeab060f 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -863,13 +863,13 @@ export default class AppTile extends React.Component { { /* Minimise widget */ } { showMinimiseButton && } { /* Maximise widget */ } { showMaximiseButton && } { /* Title */ } diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index 9d20dc1fe1..95b159deed 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -99,7 +99,7 @@ const AppsSection: React.FC = ({ room }) => { } }; - return + return { apps.map(app => { const name = WidgetUtils.getWidgetName(app); const dataTitle = WidgetUtils.getWidgetDataTitle(app); @@ -161,7 +161,7 @@ const AppsSection: React.FC = ({ room }) => { }) } - { apps.length > 0 ? _t("Edit apps, bridges & bots") : _t("Add apps, bridges & bots") } + { apps.length > 0 ? _t("Edit widgets, bridges & bots") : _t("Add widgets, bridges & bots") } ; }; diff --git a/src/components/views/right_panel/WidgetCard.tsx b/src/components/views/right_panel/WidgetCard.tsx index dec30a57f2..1677494708 100644 --- a/src/components/views/right_panel/WidgetCard.tsx +++ b/src/components/views/right_panel/WidgetCard.tsx @@ -152,7 +152,7 @@ const WidgetCard: React.FC = ({ room, widgetId, onClose }) => { ; } else { pinButton = Date: Fri, 18 Sep 2020 17:13:45 +0100 Subject: [PATCH 280/286] Fix New Room List arrow key management Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/LeftPanel.tsx | 2 +- src/components/structures/RoomSearch.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index 1c2295384c..090a64904c 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -52,7 +52,7 @@ interface IState { // List of CSS classes which should be included in keyboard navigation within the room list const cssClasses = [ "mx_RoomSearch_input", - "mx_RoomSearch_icon", // minimized + "mx_RoomSearch_minimizedHandle", // minimized "mx_RoomSublist_headerText", "mx_RoomTile", "mx_RoomSublist_showNButton", diff --git a/src/components/structures/RoomSearch.tsx b/src/components/structures/RoomSearch.tsx index 768bc38d23..526aecddd7 100644 --- a/src/components/structures/RoomSearch.tsx +++ b/src/components/structures/RoomSearch.tsx @@ -165,7 +165,7 @@ export default class RoomSearch extends React.PureComponent { icon = ( ); From 6f7d6f27f1bf1f3cfcc73179a21553b4c76cbd1a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 18 Sep 2020 18:15:05 +0100 Subject: [PATCH 281/286] move the check Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/right_panel/UserInfo.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index a02b53b413..a9aebd9b33 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -1296,7 +1296,7 @@ const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => { const userTrust = cryptoEnabled && cli.checkUserTrust(member.userId); const userVerified = cryptoEnabled && userTrust.isCrossSigningVerified(); const isMe = member.userId === cli.getUserId(); - const canVerify = cryptoEnabled && homeserverSupportsCrossSigning && !userVerified && !isMe; + const canVerify = cryptoEnabled && homeserverSupportsCrossSigning && !userVerified && !isMe && devices.length > 0; const setUpdating = (updating) => { setPendingUpdateCount(count => count + (updating ? 1 : -1)); @@ -1306,7 +1306,7 @@ const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => { const showDeviceListSpinner = devices === undefined; if (canVerify) { - if (hasCrossSigningKeys !== undefined && devices.length > 0) { + if (hasCrossSigningKeys !== undefined) { // Note: mx_UserInfo_verifyButton is for the end-to-end tests verifyButton = ( { From 5630f3571533d6e1b9f86f40f8a3c3ec4029d94d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 18 Sep 2020 11:33:02 -0600 Subject: [PATCH 282/286] Add a UI feature to disable advanced encryption options --- .../views/settings/E2eAdvancedPanel.js | 5 ++ .../tabs/room/SecurityRoomSettingsTab.js | 12 +++-- .../tabs/user/SecurityUserSettingsTab.js | 35 ++++++++---- src/settings/Settings.ts | 18 +++++-- src/settings/UIFeature.ts | 1 + .../controllers/OrderedMultiController.ts | 54 +++++++++++++++++++ 6 files changed, 108 insertions(+), 17 deletions(-) create mode 100644 src/settings/controllers/OrderedMultiController.ts diff --git a/src/components/views/settings/E2eAdvancedPanel.js b/src/components/views/settings/E2eAdvancedPanel.js index 0650630901..a8764fa855 100644 --- a/src/components/views/settings/E2eAdvancedPanel.js +++ b/src/components/views/settings/E2eAdvancedPanel.js @@ -19,6 +19,7 @@ import React from 'react'; import * as sdk from '../../../index'; import {_t} from "../../../languageHandler"; import {SettingLevel} from "../../../settings/SettingLevel"; +import SettingsStore from "../../../settings/SettingsStore"; const SETTING_MANUALLY_VERIFY_ALL_SESSIONS = "e2ee.manuallyVerifyAllSessions"; @@ -37,3 +38,7 @@ const E2eAdvancedPanel = props => { }; export default E2eAdvancedPanel; + +export function isE2eAdvancedPanelPossible(): boolean { + return SettingsStore.isEnabled(SETTING_MANUALLY_VERIFY_ALL_SESSIONS); +} diff --git a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.js b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.js index 48115146f1..0a0c693158 100644 --- a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.js +++ b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.js @@ -24,6 +24,7 @@ import Modal from "../../../../../Modal"; import QuestionDialog from "../../../dialogs/QuestionDialog"; import StyledRadioGroup from '../../../elements/StyledRadioGroup'; import {SettingLevel} from "../../../../../settings/SettingLevel"; +import SettingsStore from "../../../../../settings/SettingsStore"; export default class SecurityRoomSettingsTab extends React.Component { static propTypes = { @@ -340,10 +341,13 @@ export default class SecurityRoomSettingsTab extends React.Component { const canEnableEncryption = !isEncrypted && hasEncryptionPermission; let encryptionSettings = null; - if (isEncrypted) { - encryptionSettings = ; + if (isEncrypted && SettingsStore.isEnabled("blacklistUnverifiedDevices")) { + encryptionSettings = ; } return ( diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js index 9984baeb13..61402e8881 100644 --- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js @@ -32,6 +32,7 @@ import {SettingLevel} from "../../../../../settings/SettingLevel"; import SecureBackupPanel from "../../SecureBackupPanel"; import SettingsStore from "../../../../../settings/SettingsStore"; import {UIFeature} from "../../../../../settings/UIFeature"; +import {isE2eAdvancedPanelPossible} from "../../E2eAdvancedPanel"; export class IgnoredUser extends React.Component { static propTypes = { @@ -219,6 +220,15 @@ export default class SecurityUserSettingsTab extends React.Component { ); } + let noSendUnverifiedSetting; + if (SettingsStore.isEnabled("blacklistUnverifiedDevices")) { + noSendUnverifiedSetting = ; + } + return (
    {_t("Cryptography")} @@ -233,8 +243,7 @@ export default class SecurityUserSettingsTab extends React.Component { {importExportButtons} - + {noSendUnverifiedSetting}
    ); } @@ -355,14 +364,20 @@ export default class SecurityUserSettingsTab extends React.Component { const E2eAdvancedPanel = sdk.getComponent('views.settings.E2eAdvancedPanel'); let advancedSection; if (SettingsStore.getValue(UIFeature.AdvancedSettings)) { - advancedSection = <> -
    {_t("Advanced")}
    -
    - {this._renderIgnoredUsers()} - {this._renderManageInvites()} - -
    - ; + const ignoreUsersPanel = this._renderIgnoredUsers(); + const invitesPanel = this._renderManageInvites(); + const e2ePanel = isE2eAdvancedPanelPossible() ? : null; + // only show the section if there's something to show + if (ignoreUsersPanel || invitesPanel || e2ePanel) { + advancedSection = <> +
    {_t("Advanced")}
    +
    + {ignoreUsersPanel} + {invitesPanel} + {e2ePanel} +
    + ; + } } return ( diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 16366aaa01..737c882919 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -34,6 +34,7 @@ import SettingController from "./controllers/SettingController"; import { RightPanelPhases } from "../stores/RightPanelStorePhases"; import UIFeatureController from "./controllers/UIFeatureController"; import { UIFeature } from "./UIFeature"; +import { OrderedMultiController } from "./controllers/OrderedMultiController"; // These are just a bunch of helper arrays to avoid copy/pasting a bunch of times const LEVELS_ROOM_SETTINGS = [ @@ -436,6 +437,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { "room-device": _td('Never send encrypted messages to unverified sessions in this room from this session'), }, default: false, + controller: new UIFeatureController(UIFeature.AdvancedEncryption), }, "urlPreviewsEnabled": { supportedLevels: LEVELS_ROOM_SETTINGS_WITH_ROOM, @@ -591,9 +593,15 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, displayName: _td("Manually verify all remote sessions"), default: false, - controller: new PushToMatrixClientController( - MatrixClient.prototype.setCryptoTrustCrossSignedDevices, true, - ), + controller: new OrderedMultiController([ + // Apply the feature controller first to ensure that the setting doesn't + // show up and can't be toggled. PushToMatrixClientController doesn't + // do any overrides anyways. + new UIFeatureController(UIFeature.AdvancedEncryption), + new PushToMatrixClientController( + MatrixClient.prototype.setCryptoTrustCrossSignedDevices, true, + ), + ]), }, "ircDisplayNameWidth": { // We specifically want to have room-device > device so that users may set a device default @@ -612,6 +620,10 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_ROOM_OR_ACCOUNT, default: {}, }, + [UIFeature.AdvancedEncryption]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, [UIFeature.URLPreviews]: { supportedLevels: LEVELS_UI_FEATURE, default: true, diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index 57aefa3a78..231752e19c 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -16,6 +16,7 @@ limitations under the License. // see settings.md for documentation on conventions export enum UIFeature { + AdvancedEncryption = "UIFeature.advancedEncryption", URLPreviews = "UIFeature.urlPreviews", Widgets = "UIFeature.widgets", Voip = "UIFeature.voip", diff --git a/src/settings/controllers/OrderedMultiController.ts b/src/settings/controllers/OrderedMultiController.ts new file mode 100644 index 0000000000..2f093fe25a --- /dev/null +++ b/src/settings/controllers/OrderedMultiController.ts @@ -0,0 +1,54 @@ +/* +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 { SettingLevel } from "../SettingLevel"; + +/** + * Allows for multiple controllers to affect a setting. The first controller + * provided to this class which overrides the setting value will affect + * the value - other controllers are not called. Change notification handlers + * are proxied through to all controllers. + * + * Similarly, the first controller which indicates that a setting is disabled + * will be used - other controllers will not be considered. + */ +export class OrderedMultiController extends SettingController { + constructor(public readonly controllers: SettingController[]) { + super(); + } + + public getValueOverride(level: SettingLevel, roomId: string, calculatedValue: any, calculatedAtLevel: SettingLevel): any { + for (const controller of this.controllers) { + const override = controller.getValueOverride(level, roomId, calculatedValue, calculatedAtLevel); + if (override !== undefined && override !== null) return override; + } + return null; // no override + } + + public onChange(level: SettingLevel, roomId: string, newValue: any) { + for (const controller of this.controllers) { + controller.onChange(level, roomId, newValue); + } + } + + public get settingDisabled(): boolean { + for (const controller of this.controllers) { + if (controller.settingDisabled) return true; + } + return false; + } +} From a90cf46fefc7bb162bdb0d25c05367fb2ef701a9 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 18 Sep 2020 11:39:41 -0600 Subject: [PATCH 283/286] Appease the linter --- src/settings/controllers/OrderedMultiController.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/settings/controllers/OrderedMultiController.ts b/src/settings/controllers/OrderedMultiController.ts index 2f093fe25a..fb94d6d7ef 100644 --- a/src/settings/controllers/OrderedMultiController.ts +++ b/src/settings/controllers/OrderedMultiController.ts @@ -31,7 +31,12 @@ export class OrderedMultiController extends SettingController { super(); } - public getValueOverride(level: SettingLevel, roomId: string, calculatedValue: any, calculatedAtLevel: SettingLevel): any { + public getValueOverride( + level: SettingLevel, + roomId: string, + calculatedValue: any, + calculatedAtLevel: SettingLevel, + ): any { for (const controller of this.controllers) { const override = controller.getValueOverride(level, roomId, calculatedValue, calculatedAtLevel); if (override !== undefined && override !== null) return override; From cf7b4dd3113577a4717e7c8de57928accd8eea04 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 18 Sep 2020 19:09:45 +0100 Subject: [PATCH 284/286] Add isEnabled comment Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/settings/SettingsStore.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/settings/SettingsStore.ts b/src/settings/SettingsStore.ts index 498a2d269d..85d5e1702b 100644 --- a/src/settings/SettingsStore.ts +++ b/src/settings/SettingsStore.ts @@ -257,6 +257,12 @@ export default class SettingsStore { return SETTINGS[settingName].isFeature; } + /** + * Determines if a setting is enabled. + * If a setting is disabled then it should be hidden from the user. + * @param {string} settingName The setting to look up. + * @return {boolean} True if the setting is enabled. + */ public static isEnabled(settingName: string) { if (!SETTINGS[settingName]) return false; return SETTINGS[settingName].controller ? !SETTINGS[settingName].controller.settingDisabled : true; From 8e9ff05762f9176b66168fc17dea68f01f0ced96 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 18 Sep 2020 19:14:24 +0100 Subject: [PATCH 285/286] Update src/settings/SettingsStore.ts Co-authored-by: Travis Ralston --- src/settings/SettingsStore.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/SettingsStore.ts b/src/settings/SettingsStore.ts index 85d5e1702b..7c05e4b500 100644 --- a/src/settings/SettingsStore.ts +++ b/src/settings/SettingsStore.ts @@ -263,7 +263,7 @@ export default class SettingsStore { * @param {string} settingName The setting to look up. * @return {boolean} True if the setting is enabled. */ - public static isEnabled(settingName: string) { + public static isEnabled(settingName: string): boolean { if (!SETTINGS[settingName]) return false; return SETTINGS[settingName].controller ? !SETTINGS[settingName].controller.settingDisabled : true; } From 42cdf4b7c9b1491bfe1c2f2e6cacba5888dd3dbb Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 21 Sep 2020 13:57:33 +0100 Subject: [PATCH 286/286] fix undefined devices case Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/right_panel/UserInfo.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index a9aebd9b33..8440532b9d 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -1296,7 +1296,8 @@ const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => { const userTrust = cryptoEnabled && cli.checkUserTrust(member.userId); const userVerified = cryptoEnabled && userTrust.isCrossSigningVerified(); const isMe = member.userId === cli.getUserId(); - const canVerify = cryptoEnabled && homeserverSupportsCrossSigning && !userVerified && !isMe && devices.length > 0; + const canVerify = cryptoEnabled && homeserverSupportsCrossSigning && !userVerified && !isMe && + devices && devices.length > 0; const setUpdating = (updating) => { setPendingUpdateCount(count => count + (updating ? 1 : -1));